libreclient 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libreclient/__init__.py +10 -0
- libreclient/__init__.pyi +4 -0
- libreclient/_base_client.py +70 -0
- libreclient/_route_types.py +0 -0
- libreclient/client.py +325 -0
- libreclient/config.py +59 -0
- libreclient/models/__init__.py +83 -0
- libreclient/models/_base.py +35 -0
- libreclient/models/alerts.py +31 -0
- libreclient/models/arp.py +13 -0
- libreclient/models/bills.py +21 -0
- libreclient/models/device_groups.py +19 -0
- libreclient/models/devices.py +41 -0
- libreclient/models/index.py +15 -0
- libreclient/models/inventory.py +15 -0
- libreclient/models/locations.py +15 -0
- libreclient/models/logs.py +13 -0
- libreclient/models/poller_groups.py +15 -0
- libreclient/models/pollers.py +13 -0
- libreclient/models/port_groups.py +13 -0
- libreclient/models/port_security.py +15 -0
- libreclient/models/portgroups.py +9 -0
- libreclient/models/ports.py +41 -0
- libreclient/models/routing.py +13 -0
- libreclient/models/services.py +18 -0
- libreclient/models/switching.py +15 -0
- libreclient/models/system.py +13 -0
- libreclient/py.typed +0 -0
- libreclient/routes/__init__.py +79 -0
- libreclient/routes/__init__.pyi +79 -0
- libreclient/routes/_synchronicity.py +7 -0
- libreclient/routes/_types.py +65 -0
- libreclient/routes/alerts.py +215 -0
- libreclient/routes/alerts.pyi +220 -0
- libreclient/routes/arp.py +35 -0
- libreclient/routes/arp.pyi +30 -0
- libreclient/routes/bills.py +167 -0
- libreclient/routes/bills.pyi +166 -0
- libreclient/routes/device_groups.py +204 -0
- libreclient/routes/device_groups.pyi +168 -0
- libreclient/routes/devices.py +720 -0
- libreclient/routes/devices.pyi +692 -0
- libreclient/routes/index.py +25 -0
- libreclient/routes/index.pyi +24 -0
- libreclient/routes/inventory.py +58 -0
- libreclient/routes/inventory.pyi +46 -0
- libreclient/routes/locations.py +119 -0
- libreclient/routes/locations.pyi +113 -0
- libreclient/routes/logs.py +165 -0
- libreclient/routes/logs.pyi +137 -0
- libreclient/routes/poller_groups.py +34 -0
- libreclient/routes/poller_groups.pyi +28 -0
- libreclient/routes/pollers.py +40 -0
- libreclient/routes/pollers.pyi +38 -0
- libreclient/routes/port_groups.py +87 -0
- libreclient/routes/port_groups.pyi +87 -0
- libreclient/routes/port_security.py +51 -0
- libreclient/routes/port_security.pyi +52 -0
- libreclient/routes/portgroups.py +64 -0
- libreclient/routes/portgroups.pyi +57 -0
- libreclient/routes/ports.py +138 -0
- libreclient/routes/ports.pyi +132 -0
- libreclient/routes/routing.py +247 -0
- libreclient/routes/routing.pyi +242 -0
- libreclient/routes/services.py +108 -0
- libreclient/routes/services.pyi +102 -0
- libreclient/routes/switching.py +98 -0
- libreclient/routes/switching.pyi +112 -0
- libreclient/routes/system.py +36 -0
- libreclient/routes/system.pyi +35 -0
- libreclient-0.1.0.dist-info/METADATA +319 -0
- libreclient-0.1.0.dist-info/RECORD +73 -0
- libreclient-0.1.0.dist-info/WHEEL +4 -0
libreclient/__init__.py
ADDED
libreclient/__init__.pyi
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Shared base client functionality for sync/async LibreNMS clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
from .config import LibreConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseLibreClient(LibreConfig, ABC):
|
|
13
|
+
"""Common transport helpers used by both sync and async clients.
|
|
14
|
+
|
|
15
|
+
Route namespaces implemented as async code can call these methods and work
|
|
16
|
+
with either concrete client implementation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def _api_url(self, path: str) -> str:
|
|
20
|
+
suffix = path if path.startswith("/") else f"/{path}"
|
|
21
|
+
return f"{self.base_url}{suffix}"
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
async def _request_raw(self, method: str, url: str, **kwargs: Any) -> Any:
|
|
25
|
+
"""Execute one HTTP request and return the raw response object."""
|
|
26
|
+
|
|
27
|
+
async def _request(
|
|
28
|
+
self, method: str, path: str, **kwargs: Any
|
|
29
|
+
) -> dict | list:
|
|
30
|
+
response = await self._request_raw(
|
|
31
|
+
method, self._api_url(path), **kwargs
|
|
32
|
+
)
|
|
33
|
+
return await _json_from_response(response)
|
|
34
|
+
|
|
35
|
+
async def _request_bytes(
|
|
36
|
+
self, method: str, path: str, **kwargs: Any
|
|
37
|
+
) -> bytes:
|
|
38
|
+
response = await self._request_raw(
|
|
39
|
+
method, self._api_url(path), **kwargs
|
|
40
|
+
)
|
|
41
|
+
payload = response.content
|
|
42
|
+
if inspect.isawaitable(payload):
|
|
43
|
+
payload = await payload
|
|
44
|
+
return cast("bytes", payload)
|
|
45
|
+
|
|
46
|
+
async def _get(self, path: str, **kwargs: Any) -> dict | list:
|
|
47
|
+
return await self._request("GET", path, **kwargs)
|
|
48
|
+
|
|
49
|
+
async def _post(self, path: str, **kwargs: Any) -> dict:
|
|
50
|
+
return await self._request("POST", path, **kwargs)
|
|
51
|
+
|
|
52
|
+
async def _put(self, path: str, **kwargs: Any) -> dict:
|
|
53
|
+
return await self._request("PUT", path, **kwargs)
|
|
54
|
+
|
|
55
|
+
async def _patch(self, path: str, **kwargs: Any) -> dict:
|
|
56
|
+
return await self._request("PATCH", path, **kwargs)
|
|
57
|
+
|
|
58
|
+
async def _delete(self, path: str, **kwargs: Any) -> dict:
|
|
59
|
+
return await self._request("DELETE", path, **kwargs)
|
|
60
|
+
|
|
61
|
+
async def _get_bytes(self, path: str, **kwargs: Any) -> bytes:
|
|
62
|
+
return await self._request_bytes("GET", path, **kwargs)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _json_from_response(response: Any) -> dict | list:
|
|
66
|
+
"""Normalize niquests response.json() for sync/async response types."""
|
|
67
|
+
payload = response.json()
|
|
68
|
+
if inspect.isawaitable(payload):
|
|
69
|
+
payload = await payload
|
|
70
|
+
return cast("dict", payload)
|
|
File without changes
|
libreclient/client.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LibreNMS API clients — synchronous and asynchronous.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import niquests
|
|
8
|
+
from pydantic import PrivateAttr
|
|
9
|
+
|
|
10
|
+
from ._base_client import BaseLibreClient
|
|
11
|
+
from .routes import (
|
|
12
|
+
AlertsAsync,
|
|
13
|
+
AlertsSync,
|
|
14
|
+
ArpAsync,
|
|
15
|
+
ArpSync,
|
|
16
|
+
BillsAsync,
|
|
17
|
+
BillsSync,
|
|
18
|
+
DeviceGroupsAsync,
|
|
19
|
+
DeviceGroupsSync,
|
|
20
|
+
DevicesAsync,
|
|
21
|
+
DevicesSync,
|
|
22
|
+
IndexAsync,
|
|
23
|
+
IndexSync,
|
|
24
|
+
InventoryAsync,
|
|
25
|
+
InventorySync,
|
|
26
|
+
LocationsAsync,
|
|
27
|
+
LocationsSync,
|
|
28
|
+
LogsAsync,
|
|
29
|
+
LogsSync,
|
|
30
|
+
PollerGroupsAsync,
|
|
31
|
+
PollerGroupsSync,
|
|
32
|
+
PollersAsync,
|
|
33
|
+
PollersSync,
|
|
34
|
+
PortGroupsAsync,
|
|
35
|
+
PortgroupsAsync,
|
|
36
|
+
PortGroupsSync,
|
|
37
|
+
PortgroupsSync,
|
|
38
|
+
PortsAsync,
|
|
39
|
+
PortSecurityAsync,
|
|
40
|
+
PortSecuritySync,
|
|
41
|
+
PortsSync,
|
|
42
|
+
RoutingAsync,
|
|
43
|
+
RoutingSync,
|
|
44
|
+
ServicesAsync,
|
|
45
|
+
ServicesSync,
|
|
46
|
+
SwitchingAsync,
|
|
47
|
+
SwitchingSync,
|
|
48
|
+
SystemAsync,
|
|
49
|
+
SystemSync,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LibreClientSync(BaseLibreClient):
|
|
54
|
+
"""Synchronous LibreNMS API client backed by :class:`niquests.Session`."""
|
|
55
|
+
|
|
56
|
+
_session: niquests.Session = PrivateAttr()
|
|
57
|
+
_alerts: AlertsSync = PrivateAttr()
|
|
58
|
+
_arp: ArpSync = PrivateAttr()
|
|
59
|
+
_bills: BillsSync = PrivateAttr()
|
|
60
|
+
_device_groups: DeviceGroupsSync = PrivateAttr()
|
|
61
|
+
_devices: DevicesSync = PrivateAttr()
|
|
62
|
+
_index: IndexSync = PrivateAttr()
|
|
63
|
+
_inventory: InventorySync = PrivateAttr()
|
|
64
|
+
_locations: LocationsSync = PrivateAttr()
|
|
65
|
+
_logs: LogsSync = PrivateAttr()
|
|
66
|
+
_poller_groups: PollerGroupsSync = PrivateAttr()
|
|
67
|
+
_pollers: PollersSync = PrivateAttr()
|
|
68
|
+
_port_groups: PortGroupsSync = PrivateAttr()
|
|
69
|
+
_port_security: PortSecuritySync = PrivateAttr()
|
|
70
|
+
_portgroups: PortgroupsSync = PrivateAttr()
|
|
71
|
+
_ports: PortsSync = PrivateAttr()
|
|
72
|
+
_routing: RoutingSync = PrivateAttr()
|
|
73
|
+
_services: ServicesSync = PrivateAttr()
|
|
74
|
+
_switching: SwitchingSync = PrivateAttr()
|
|
75
|
+
_system: SystemSync = PrivateAttr()
|
|
76
|
+
|
|
77
|
+
def model_post_init(self, __context) -> None:
|
|
78
|
+
self._session = niquests.Session()
|
|
79
|
+
self._session.headers.update({"X-Auth-Token": self.token})
|
|
80
|
+
self._session.verify = self.verify_ssl
|
|
81
|
+
self._alerts = AlertsSync(self)
|
|
82
|
+
self._arp = ArpSync(self)
|
|
83
|
+
self._bills = BillsSync(self)
|
|
84
|
+
self._device_groups = DeviceGroupsSync(self)
|
|
85
|
+
self._devices = DevicesSync(self)
|
|
86
|
+
self._index = IndexSync(self)
|
|
87
|
+
self._inventory = InventorySync(self)
|
|
88
|
+
self._locations = LocationsSync(self)
|
|
89
|
+
self._logs = LogsSync(self)
|
|
90
|
+
self._poller_groups = PollerGroupsSync(self)
|
|
91
|
+
self._pollers = PollersSync(self)
|
|
92
|
+
self._port_groups = PortGroupsSync(self)
|
|
93
|
+
self._port_security = PortSecuritySync(self)
|
|
94
|
+
self._portgroups = PortgroupsSync(self)
|
|
95
|
+
self._ports = PortsSync(self)
|
|
96
|
+
self._routing = RoutingSync(self)
|
|
97
|
+
self._services = ServicesSync(self)
|
|
98
|
+
self._switching = SwitchingSync(self)
|
|
99
|
+
self._system = SystemSync(self)
|
|
100
|
+
|
|
101
|
+
async def _request_raw(self, method: str, url: str, **kwargs) -> Any:
|
|
102
|
+
return self._session.request(method, url, **kwargs)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def alerts(self) -> AlertsSync:
|
|
106
|
+
return self._alerts
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def arp(self) -> ArpSync:
|
|
110
|
+
return self._arp
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def bills(self) -> BillsSync:
|
|
114
|
+
return self._bills
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def device_groups(self) -> DeviceGroupsSync:
|
|
118
|
+
return self._device_groups
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def devices(self) -> DevicesSync:
|
|
122
|
+
return self._devices
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def index(self) -> IndexSync:
|
|
126
|
+
return self._index
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def inventory(self) -> InventorySync:
|
|
130
|
+
return self._inventory
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def locations(self) -> LocationsSync:
|
|
134
|
+
return self._locations
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def logs(self) -> LogsSync:
|
|
138
|
+
return self._logs
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def poller_groups(self) -> PollerGroupsSync:
|
|
142
|
+
return self._poller_groups
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def pollers(self) -> PollersSync:
|
|
146
|
+
return self._pollers
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def port_groups(self) -> PortGroupsSync:
|
|
150
|
+
return self._port_groups
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def port_security(self) -> PortSecuritySync:
|
|
154
|
+
return self._port_security
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def portgroups(self) -> PortgroupsSync:
|
|
158
|
+
return self._portgroups
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def ports(self) -> PortsSync:
|
|
162
|
+
return self._ports
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def routing(self) -> RoutingSync:
|
|
166
|
+
return self._routing
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def services(self) -> ServicesSync:
|
|
170
|
+
return self._services
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def switching(self) -> SwitchingSync:
|
|
174
|
+
return self._switching
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def system(self) -> SystemSync:
|
|
178
|
+
return self._system
|
|
179
|
+
|
|
180
|
+
def close(self) -> None:
|
|
181
|
+
self._session.close()
|
|
182
|
+
|
|
183
|
+
def __enter__(self) -> "LibreClientSync":
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def __exit__(self, *_) -> None:
|
|
187
|
+
self.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class LibreClientAsync(BaseLibreClient):
|
|
191
|
+
"""Asynchronous LibreNMS API client backed by :class:`niquests.AsyncSession`."""
|
|
192
|
+
|
|
193
|
+
_session: niquests.AsyncSession = PrivateAttr()
|
|
194
|
+
_alerts: AlertsAsync = PrivateAttr()
|
|
195
|
+
_arp: ArpAsync = PrivateAttr()
|
|
196
|
+
_bills: BillsAsync = PrivateAttr()
|
|
197
|
+
_device_groups: DeviceGroupsAsync = PrivateAttr()
|
|
198
|
+
_devices: DevicesAsync = PrivateAttr()
|
|
199
|
+
_index: IndexAsync = PrivateAttr()
|
|
200
|
+
_inventory: InventoryAsync = PrivateAttr()
|
|
201
|
+
_locations: LocationsAsync = PrivateAttr()
|
|
202
|
+
_logs: LogsAsync = PrivateAttr()
|
|
203
|
+
_poller_groups: PollerGroupsAsync = PrivateAttr()
|
|
204
|
+
_pollers: PollersAsync = PrivateAttr()
|
|
205
|
+
_port_groups: PortGroupsAsync = PrivateAttr()
|
|
206
|
+
_port_security: PortSecurityAsync = PrivateAttr()
|
|
207
|
+
_portgroups: PortgroupsAsync = PrivateAttr()
|
|
208
|
+
_ports: PortsAsync = PrivateAttr()
|
|
209
|
+
_routing: RoutingAsync = PrivateAttr()
|
|
210
|
+
_services: ServicesAsync = PrivateAttr()
|
|
211
|
+
_switching: SwitchingAsync = PrivateAttr()
|
|
212
|
+
_system: SystemAsync = PrivateAttr()
|
|
213
|
+
|
|
214
|
+
def model_post_init(self, __context) -> None:
|
|
215
|
+
self._session = niquests.AsyncSession()
|
|
216
|
+
self._session.headers.update({"X-Auth-Token": self.token})
|
|
217
|
+
self._session.verify = self.verify_ssl
|
|
218
|
+
|
|
219
|
+
self._alerts = AlertsAsync(self)
|
|
220
|
+
self._arp = ArpAsync(self)
|
|
221
|
+
self._bills = BillsAsync(self)
|
|
222
|
+
self._device_groups = DeviceGroupsAsync(self)
|
|
223
|
+
self._devices = DevicesAsync(self)
|
|
224
|
+
self._index = IndexAsync(self)
|
|
225
|
+
self._inventory = InventoryAsync(self)
|
|
226
|
+
self._locations = LocationsAsync(self)
|
|
227
|
+
self._logs = LogsAsync(self)
|
|
228
|
+
self._poller_groups = PollerGroupsAsync(self)
|
|
229
|
+
self._pollers = PollersAsync(self)
|
|
230
|
+
self._port_groups = PortGroupsAsync(self)
|
|
231
|
+
self._port_security = PortSecurityAsync(self)
|
|
232
|
+
self._portgroups = PortgroupsAsync(self)
|
|
233
|
+
self._ports = PortsAsync(self)
|
|
234
|
+
self._routing = RoutingAsync(self)
|
|
235
|
+
self._services = ServicesAsync(self)
|
|
236
|
+
self._switching = SwitchingAsync(self)
|
|
237
|
+
self._system = SystemAsync(self)
|
|
238
|
+
|
|
239
|
+
async def _request_raw(self, method: str, url: str, **kwargs) -> Any:
|
|
240
|
+
return await self._session.request(method, url, **kwargs)
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def alerts(self) -> AlertsAsync:
|
|
244
|
+
return self._alerts
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def arp(self) -> ArpAsync:
|
|
248
|
+
return self._arp
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def bills(self) -> BillsAsync:
|
|
252
|
+
return self._bills
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def device_groups(self) -> DeviceGroupsAsync:
|
|
256
|
+
return self._device_groups
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def devices(self) -> DevicesAsync:
|
|
260
|
+
return self._devices
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def index(self) -> IndexAsync:
|
|
264
|
+
return self._index
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def inventory(self) -> InventoryAsync:
|
|
268
|
+
return self._inventory
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def locations(self) -> LocationsAsync:
|
|
272
|
+
return self._locations
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def logs(self) -> LogsAsync:
|
|
276
|
+
return self._logs
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def poller_groups(self) -> PollerGroupsAsync:
|
|
280
|
+
return self._poller_groups
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def pollers(self) -> PollersAsync:
|
|
284
|
+
return self._pollers
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def port_groups(self) -> PortGroupsAsync:
|
|
288
|
+
return self._port_groups
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def port_security(self) -> PortSecurityAsync:
|
|
292
|
+
return self._port_security
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def portgroups(self) -> PortgroupsAsync:
|
|
296
|
+
return self._portgroups
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def ports(self) -> PortsAsync:
|
|
300
|
+
return self._ports
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def routing(self) -> RoutingAsync:
|
|
304
|
+
return self._routing
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def services(self) -> ServicesAsync:
|
|
308
|
+
return self._services
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def switching(self) -> SwitchingAsync:
|
|
312
|
+
return self._switching
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def system(self) -> SystemAsync:
|
|
316
|
+
return self._system
|
|
317
|
+
|
|
318
|
+
async def close(self) -> None:
|
|
319
|
+
await self._session.close()
|
|
320
|
+
|
|
321
|
+
async def __aenter__(self) -> "LibreClientAsync":
|
|
322
|
+
return self
|
|
323
|
+
|
|
324
|
+
async def __aexit__(self, *_) -> None:
|
|
325
|
+
await self.close()
|
libreclient/config.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LibreNMS client configuration via Pydantic Settings.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from pydantic import AnyHttpUrl, Field, model_validator
|
|
8
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LibreConfig(BaseSettings):
|
|
12
|
+
"""Configuration for the LibreNMS API client.
|
|
13
|
+
|
|
14
|
+
Values can be supplied directly or via environment variables prefixed
|
|
15
|
+
with ``LIBRENMS_`` (e.g. ``LIBRENMS_URL``, ``LIBRENMS_TOKEN``).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
model_config = SettingsConfigDict(
|
|
19
|
+
env_prefix="LIBRENMS_",
|
|
20
|
+
env_file=".env",
|
|
21
|
+
env_file_encoding="utf-8",
|
|
22
|
+
extra="ignore",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
url: AnyHttpUrl = Field(
|
|
26
|
+
...,
|
|
27
|
+
description="Base URL of the LibreNMS instance, e.g. https://librenms.example.com",
|
|
28
|
+
)
|
|
29
|
+
token: str = Field(..., description="LibreNMS API token (X-Auth-Token)")
|
|
30
|
+
verify_ssl: bool = Field(True, description="Verify TLS/SSL certificates")
|
|
31
|
+
api_version: str = Field(
|
|
32
|
+
"v0", description="API version segment, e.g. 'v0'"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@model_validator(mode="before")
|
|
36
|
+
@classmethod
|
|
37
|
+
def normalize_url_and_api_version(cls, data):
|
|
38
|
+
"""If `url` includes `/api/vN`, strip it from url and set `api_version`."""
|
|
39
|
+
if not isinstance(data, dict):
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
raw_url = data.get("url")
|
|
43
|
+
if raw_url is None:
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
url_str = str(raw_url)
|
|
47
|
+
# Find embedded API version path, e.g. https://host/api/v1
|
|
48
|
+
match = re.search(r"/api/(v\d+)(?:/|$)", url_str)
|
|
49
|
+
if not match:
|
|
50
|
+
return data
|
|
51
|
+
|
|
52
|
+
data["api_version"] = match.group(1)
|
|
53
|
+
data["url"] = url_str[: match.start()].rstrip("/")
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def base_url(self) -> str:
|
|
58
|
+
"""Full API base URL, e.g. https://librenms.example.com/api/v0."""
|
|
59
|
+
return f"{str(self.url).rstrip('/')}/api/{self.api_version}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Pydantic models for LibreNMS API responses.
|
|
2
|
+
|
|
3
|
+
Import from here for convenience, or directly from sub-modules for clarity.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Base models (shared across all routes)
|
|
7
|
+
from ._base import ApiResponse, ApiResponseWithId, ListResponse
|
|
8
|
+
|
|
9
|
+
# Per-route response models
|
|
10
|
+
from .alerts import (
|
|
11
|
+
AlertsResponse,
|
|
12
|
+
AlertTemplateCreatedResponse,
|
|
13
|
+
AlertTemplatesResponse,
|
|
14
|
+
RulesResponse,
|
|
15
|
+
)
|
|
16
|
+
from .arp import ArpResponse
|
|
17
|
+
from .bills import BillHistoryResponse, BillsResponse
|
|
18
|
+
from .device_groups import DeviceGroupDevicesResponse, DeviceGroupsResponse
|
|
19
|
+
from .devices import (
|
|
20
|
+
ComponentsResponse,
|
|
21
|
+
DeviceFdbResponse,
|
|
22
|
+
DevicePortsResponse,
|
|
23
|
+
DeviceResponse,
|
|
24
|
+
DevicesResponse,
|
|
25
|
+
)
|
|
26
|
+
from .index import IndexResponse
|
|
27
|
+
from .inventory import InventoryResponse
|
|
28
|
+
from .locations import LocationsResponse
|
|
29
|
+
from .logs import LogsResponse
|
|
30
|
+
from .poller_groups import PollerGroupsResponse
|
|
31
|
+
from .pollers import PollersResponse
|
|
32
|
+
from .port_groups import PortGroupsResponse
|
|
33
|
+
from .port_security import PortSecurityResponse
|
|
34
|
+
from .portgroups import PortgroupsResponse
|
|
35
|
+
from .ports import (
|
|
36
|
+
PortDescriptionResponse,
|
|
37
|
+
PortIpResponse,
|
|
38
|
+
PortResponse,
|
|
39
|
+
PortsResponse,
|
|
40
|
+
PortTransceiverResponse,
|
|
41
|
+
)
|
|
42
|
+
from .routing import RoutingResponse
|
|
43
|
+
from .services import ServicesResponse
|
|
44
|
+
from .switching import SwitchingResponse
|
|
45
|
+
from .system import SystemResponse
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AlertTemplateCreatedResponse",
|
|
49
|
+
"AlertTemplatesResponse",
|
|
50
|
+
"AlertsResponse",
|
|
51
|
+
"ApiResponse",
|
|
52
|
+
"ApiResponseWithId",
|
|
53
|
+
"ArpResponse",
|
|
54
|
+
"BillHistoryResponse",
|
|
55
|
+
"BillsResponse",
|
|
56
|
+
"ComponentsResponse",
|
|
57
|
+
"DeviceFdbResponse",
|
|
58
|
+
"DeviceGroupDevicesResponse",
|
|
59
|
+
"DeviceGroupsResponse",
|
|
60
|
+
"DevicePortsResponse",
|
|
61
|
+
"DeviceResponse",
|
|
62
|
+
"DevicesResponse",
|
|
63
|
+
"IndexResponse",
|
|
64
|
+
"InventoryResponse",
|
|
65
|
+
"ListResponse",
|
|
66
|
+
"LocationsResponse",
|
|
67
|
+
"LogsResponse",
|
|
68
|
+
"PollerGroupsResponse",
|
|
69
|
+
"PollersResponse",
|
|
70
|
+
"PortDescriptionResponse",
|
|
71
|
+
"PortGroupsResponse",
|
|
72
|
+
"PortIpResponse",
|
|
73
|
+
"PortResponse",
|
|
74
|
+
"PortSecurityResponse",
|
|
75
|
+
"PortTransceiverResponse",
|
|
76
|
+
"PortgroupsResponse",
|
|
77
|
+
"PortsResponse",
|
|
78
|
+
"RoutingResponse",
|
|
79
|
+
"RulesResponse",
|
|
80
|
+
"ServicesResponse",
|
|
81
|
+
"SwitchingResponse",
|
|
82
|
+
"SystemResponse",
|
|
83
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base response models shared across all route modules.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiResponse(BaseModel):
|
|
13
|
+
"""Base response envelope — every API call returns at least these."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
16
|
+
|
|
17
|
+
status: str = "ok"
|
|
18
|
+
message: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ApiResponseWithId(ApiResponse):
|
|
22
|
+
"""Response that includes a created/affected resource id."""
|
|
23
|
+
|
|
24
|
+
id: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ListResponse(ApiResponse):
|
|
28
|
+
"""Response containing a counted list of resources.
|
|
29
|
+
|
|
30
|
+
Subclass this and override ``data`` with the appropriate
|
|
31
|
+
``validation_alias`` for the API's response key.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
count: int = 0
|
|
35
|
+
data: list[Any] = Field(default_factory=list)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Response models for Alerts routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ._base import ApiResponseWithId, ListResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AlertsResponse(ListResponse):
|
|
11
|
+
"""Response from get_alert / list_alerts."""
|
|
12
|
+
|
|
13
|
+
data: list[dict] = Field(default_factory=list, validation_alias="alerts")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RulesResponse(ListResponse):
|
|
17
|
+
"""Response from get_alert_rule / list_alert_rules."""
|
|
18
|
+
|
|
19
|
+
data: list[dict] = Field(default_factory=list, validation_alias="rules")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AlertTemplatesResponse(ListResponse):
|
|
23
|
+
"""Response from get_alert_template / list_alert_templates."""
|
|
24
|
+
|
|
25
|
+
data: list[dict] = Field(
|
|
26
|
+
default_factory=list, validation_alias="alert_templates"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AlertTemplateCreatedResponse(ApiResponseWithId):
|
|
31
|
+
"""Response from add_alert_template (returns the new template id)."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Response models for ARP routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ._base import ListResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ArpResponse(ListResponse):
|
|
11
|
+
"""Response from list_arp."""
|
|
12
|
+
|
|
13
|
+
data: list[dict] = Field(default_factory=list, validation_alias="arp")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Response models for Bills routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ._base import ListResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BillsResponse(ListResponse):
|
|
11
|
+
"""Response from list_bills / get_bill."""
|
|
12
|
+
|
|
13
|
+
data: list[dict] = Field(default_factory=list, validation_alias="bills")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BillHistoryResponse(ListResponse):
|
|
17
|
+
"""Response from get_bill_history."""
|
|
18
|
+
|
|
19
|
+
data: list[dict] = Field(
|
|
20
|
+
default_factory=list, validation_alias="bill_history"
|
|
21
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Response models for Device Groups routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from ._base import ListResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DeviceGroupsResponse(ListResponse):
|
|
11
|
+
"""Response from get_devicegroups."""
|
|
12
|
+
|
|
13
|
+
data: list[dict] = Field(default_factory=list, validation_alias="groups")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeviceGroupDevicesResponse(ListResponse):
|
|
17
|
+
"""Response from get_devices_by_group."""
|
|
18
|
+
|
|
19
|
+
data: list[dict] = Field(default_factory=list, validation_alias="devices")
|