libreclient 0.1.0__tar.gz
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-0.1.0/PKG-INFO +319 -0
- libreclient-0.1.0/README.md +301 -0
- libreclient-0.1.0/pyproject.toml +96 -0
- libreclient-0.1.0/src/libreclient/__init__.py +10 -0
- libreclient-0.1.0/src/libreclient/__init__.pyi +4 -0
- libreclient-0.1.0/src/libreclient/_base_client.py +70 -0
- libreclient-0.1.0/src/libreclient/_route_types.py +0 -0
- libreclient-0.1.0/src/libreclient/client.py +325 -0
- libreclient-0.1.0/src/libreclient/config.py +59 -0
- libreclient-0.1.0/src/libreclient/models/__init__.py +83 -0
- libreclient-0.1.0/src/libreclient/models/_base.py +35 -0
- libreclient-0.1.0/src/libreclient/models/alerts.py +31 -0
- libreclient-0.1.0/src/libreclient/models/arp.py +13 -0
- libreclient-0.1.0/src/libreclient/models/bills.py +21 -0
- libreclient-0.1.0/src/libreclient/models/device_groups.py +19 -0
- libreclient-0.1.0/src/libreclient/models/devices.py +41 -0
- libreclient-0.1.0/src/libreclient/models/index.py +15 -0
- libreclient-0.1.0/src/libreclient/models/inventory.py +15 -0
- libreclient-0.1.0/src/libreclient/models/locations.py +15 -0
- libreclient-0.1.0/src/libreclient/models/logs.py +13 -0
- libreclient-0.1.0/src/libreclient/models/poller_groups.py +15 -0
- libreclient-0.1.0/src/libreclient/models/pollers.py +13 -0
- libreclient-0.1.0/src/libreclient/models/port_groups.py +13 -0
- libreclient-0.1.0/src/libreclient/models/port_security.py +15 -0
- libreclient-0.1.0/src/libreclient/models/portgroups.py +9 -0
- libreclient-0.1.0/src/libreclient/models/ports.py +41 -0
- libreclient-0.1.0/src/libreclient/models/routing.py +13 -0
- libreclient-0.1.0/src/libreclient/models/services.py +18 -0
- libreclient-0.1.0/src/libreclient/models/switching.py +15 -0
- libreclient-0.1.0/src/libreclient/models/system.py +13 -0
- libreclient-0.1.0/src/libreclient/py.typed +0 -0
- libreclient-0.1.0/src/libreclient/routes/__init__.py +79 -0
- libreclient-0.1.0/src/libreclient/routes/__init__.pyi +79 -0
- libreclient-0.1.0/src/libreclient/routes/_synchronicity.py +7 -0
- libreclient-0.1.0/src/libreclient/routes/_types.py +65 -0
- libreclient-0.1.0/src/libreclient/routes/alerts.py +215 -0
- libreclient-0.1.0/src/libreclient/routes/alerts.pyi +220 -0
- libreclient-0.1.0/src/libreclient/routes/arp.py +35 -0
- libreclient-0.1.0/src/libreclient/routes/arp.pyi +30 -0
- libreclient-0.1.0/src/libreclient/routes/bills.py +167 -0
- libreclient-0.1.0/src/libreclient/routes/bills.pyi +166 -0
- libreclient-0.1.0/src/libreclient/routes/device_groups.py +204 -0
- libreclient-0.1.0/src/libreclient/routes/device_groups.pyi +168 -0
- libreclient-0.1.0/src/libreclient/routes/devices.py +720 -0
- libreclient-0.1.0/src/libreclient/routes/devices.pyi +692 -0
- libreclient-0.1.0/src/libreclient/routes/index.py +25 -0
- libreclient-0.1.0/src/libreclient/routes/index.pyi +24 -0
- libreclient-0.1.0/src/libreclient/routes/inventory.py +58 -0
- libreclient-0.1.0/src/libreclient/routes/inventory.pyi +46 -0
- libreclient-0.1.0/src/libreclient/routes/locations.py +119 -0
- libreclient-0.1.0/src/libreclient/routes/locations.pyi +113 -0
- libreclient-0.1.0/src/libreclient/routes/logs.py +165 -0
- libreclient-0.1.0/src/libreclient/routes/logs.pyi +137 -0
- libreclient-0.1.0/src/libreclient/routes/poller_groups.py +34 -0
- libreclient-0.1.0/src/libreclient/routes/poller_groups.pyi +28 -0
- libreclient-0.1.0/src/libreclient/routes/pollers.py +40 -0
- libreclient-0.1.0/src/libreclient/routes/pollers.pyi +38 -0
- libreclient-0.1.0/src/libreclient/routes/port_groups.py +87 -0
- libreclient-0.1.0/src/libreclient/routes/port_groups.pyi +87 -0
- libreclient-0.1.0/src/libreclient/routes/port_security.py +51 -0
- libreclient-0.1.0/src/libreclient/routes/port_security.pyi +52 -0
- libreclient-0.1.0/src/libreclient/routes/portgroups.py +64 -0
- libreclient-0.1.0/src/libreclient/routes/portgroups.pyi +57 -0
- libreclient-0.1.0/src/libreclient/routes/ports.py +138 -0
- libreclient-0.1.0/src/libreclient/routes/ports.pyi +132 -0
- libreclient-0.1.0/src/libreclient/routes/routing.py +247 -0
- libreclient-0.1.0/src/libreclient/routes/routing.pyi +242 -0
- libreclient-0.1.0/src/libreclient/routes/services.py +108 -0
- libreclient-0.1.0/src/libreclient/routes/services.pyi +102 -0
- libreclient-0.1.0/src/libreclient/routes/switching.py +98 -0
- libreclient-0.1.0/src/libreclient/routes/switching.pyi +112 -0
- libreclient-0.1.0/src/libreclient/routes/system.py +36 -0
- libreclient-0.1.0/src/libreclient/routes/system.pyi +35 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libreclient
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async and sync Python client for the LibreNMS API
|
|
5
|
+
Author: Justin Jeffery
|
|
6
|
+
Author-email: Justin Jeffery <34625666+jjeff07@users.noreply.github.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: niquests>=3.19.0
|
|
9
|
+
Requires-Dist: pydantic>=2.13.4
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.14.1
|
|
11
|
+
Requires-Dist: synchronicity>=0.12.3
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
|
+
Project-URL: Homepage, https://github.com/jjeff07/libreclient
|
|
14
|
+
Project-URL: Repository, https://github.com/jjeff07/libreclient.git
|
|
15
|
+
Project-URL: Issues, https://github.com/jjeff07/libreclient/issues
|
|
16
|
+
Project-URL: Changelog, https://github.com/jjeff07/libreclient/blob/main/CHANGELOG.md
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# libreclient
|
|
20
|
+
|
|
21
|
+
[](LICENSE)
|
|
22
|
+
[](https://www.python.org/downloads/)
|
|
23
|
+
|
|
24
|
+
Async and sync Python client for the [LibreNMS](https://www.librenms.org/) API.
|
|
25
|
+
|
|
26
|
+
- **Dual interface** — use `LibreClientAsync` for async/await or `LibreClientSync` for traditional blocking calls.
|
|
27
|
+
- **Typed responses** — all endpoints return Pydantic models with full IDE autocomplete.
|
|
28
|
+
- **Environment-driven config** — configure via `LIBRENMS_URL` and `LIBRENMS_TOKEN` env vars or pass values directly.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install libreclient
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv add libreclient
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### Synchronous
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from libreclient import LibreClientSync
|
|
48
|
+
|
|
49
|
+
client = LibreClientSync(url="https://librenms.example.com", token="your-api-token")
|
|
50
|
+
|
|
51
|
+
# List all devices
|
|
52
|
+
response = client.devices.list_devices()
|
|
53
|
+
for device in response.devices:
|
|
54
|
+
print(device["hostname"])
|
|
55
|
+
|
|
56
|
+
# Get a specific alert
|
|
57
|
+
alert = client.alerts.get_alert(42)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Asynchronous
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import asyncio
|
|
64
|
+
from libreclient import LibreClientAsync
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def main():
|
|
68
|
+
client = LibreClientAsync(url="https://librenms.example.com", token="your-api-token")
|
|
69
|
+
|
|
70
|
+
response = await client.devices.list_devices()
|
|
71
|
+
for device in response.devices:
|
|
72
|
+
print(device["hostname"])
|
|
73
|
+
|
|
74
|
+
await client.close()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Context Manager
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# Sync
|
|
84
|
+
with LibreClientSync(url="https://librenms.example.com", token="your-api-token") as client:
|
|
85
|
+
print(client.system.ping())
|
|
86
|
+
|
|
87
|
+
# Async
|
|
88
|
+
async with LibreClientAsync(url="https://librenms.example.com", token="your-api-token") as client:
|
|
89
|
+
print(await client.system.ping())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
Configuration is handled by [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). You can
|
|
95
|
+
pass values directly or set environment variables:
|
|
96
|
+
|
|
97
|
+
| Env Variable | Description | Default |
|
|
98
|
+
|------------------------|------------------------------------|--------------|
|
|
99
|
+
| `LIBRENMS_URL` | Base URL of your LibreNMS instance | *(required)* |
|
|
100
|
+
| `LIBRENMS_TOKEN` | API token (`X-Auth-Token`) | *(required)* |
|
|
101
|
+
| `LIBRENMS_VERIFY_SSL` | Verify TLS certificates | `true` |
|
|
102
|
+
| `LIBRENMS_API_VERSION` | API version path segment | `v0` |
|
|
103
|
+
|
|
104
|
+
A `.env` file in your working directory is also supported. Copy the included sample to get started:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
cp sample.env .env
|
|
108
|
+
# Edit .env with your LibreNMS URL and API token
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Available Route Namespaces
|
|
112
|
+
|
|
113
|
+
All route namespaces are accessible as properties on the client:
|
|
114
|
+
|
|
115
|
+
| Property | Description |
|
|
116
|
+
|------------------------|--------------------------------------------|
|
|
117
|
+
| `client.alerts` | Alert management and alert rules/templates |
|
|
118
|
+
| `client.arp` | ARP table lookups |
|
|
119
|
+
| `client.bills` | Billing data and graphs |
|
|
120
|
+
| `client.device_groups` | Device group management |
|
|
121
|
+
| `client.devices` | Device CRUD, discovery, components, graphs |
|
|
122
|
+
| `client.index` | List available API endpoints |
|
|
123
|
+
| `client.inventory` | Hardware inventory |
|
|
124
|
+
| `client.locations` | Location management |
|
|
125
|
+
| `client.logs` | Event, syslog, alert, and auth logs |
|
|
126
|
+
| `client.poller_groups` | Poller group info |
|
|
127
|
+
| `client.pollers` | Poller status |
|
|
128
|
+
| `client.port_groups` | Port group management |
|
|
129
|
+
| `client.port_security` | Port security (802.1X/MAB) |
|
|
130
|
+
| `client.ports` | Port info, search, and descriptions |
|
|
131
|
+
| `client.routing` | BGP, OSPF, VRF, MPLS, IPsec |
|
|
132
|
+
| `client.services` | Service monitoring |
|
|
133
|
+
| `client.switching` | VLANs, links, FDB, NAC |
|
|
134
|
+
| `client.system` | Ping and system info |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
### Prerequisites
|
|
141
|
+
|
|
142
|
+
- Python 3.12+
|
|
143
|
+
- [uv](https://docs.astral.sh/uv/) (package manager)
|
|
144
|
+
|
|
145
|
+
### Setup
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
git clone https://github.com/jjeff07/libreclient.git
|
|
149
|
+
cd libreclient
|
|
150
|
+
uv sync
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Running Tests
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Unit tests
|
|
157
|
+
uv run pytest tests/unit
|
|
158
|
+
|
|
159
|
+
# Functional tests (requires .env with LIBRENMS_URL and LIBRENMS_TOKEN)
|
|
160
|
+
uv run pytest tests/functional
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Linting & Formatting
|
|
164
|
+
|
|
165
|
+
This project uses [Ruff](https://docs.astral.sh/ruff/) for both linting and formatting:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Check for lint issues
|
|
169
|
+
uv run ruff check
|
|
170
|
+
|
|
171
|
+
# Auto-fix lint issues
|
|
172
|
+
uv run ruff check --fix
|
|
173
|
+
|
|
174
|
+
# Format code
|
|
175
|
+
uv run ruff format
|
|
176
|
+
|
|
177
|
+
# Check formatting without changing files
|
|
178
|
+
uv run ruff format --check
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Complexity Checks
|
|
182
|
+
|
|
183
|
+
[complexipy](https://github.com/rohaquinlop/complexipy) is used to enforce a maximum cognitive complexity of 15 per
|
|
184
|
+
function:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
uv run complexipy .
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Results are output to `complexipy-results.json`. Any function exceeding the threshold will cause the check to fail.
|
|
191
|
+
|
|
192
|
+
### Architecture
|
|
193
|
+
|
|
194
|
+
The project uses a single-implementation pattern: each route is written **once** as an async class.
|
|
195
|
+
The [synchronicity](https://github.com/modal-com/synchronicity) library then wraps each async class to produce a
|
|
196
|
+
synchronous counterpart at runtime.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
src/py_librenms/routes/alerts.py
|
|
200
|
+
├── class Alerts ← async implementation (the only code you write)
|
|
201
|
+
└── AlertsSync = synchronizer.wrap(Alerts, ...) ← sync wrapper (auto-generated at import)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This means:
|
|
205
|
+
|
|
206
|
+
- You only maintain one implementation per route.
|
|
207
|
+
- Both `LibreClientAsync` and `LibreClientSync` share the same logic.
|
|
208
|
+
- No code duplication between sync and async interfaces.
|
|
209
|
+
|
|
210
|
+
### Type Stubs
|
|
211
|
+
|
|
212
|
+
Because `synchronicity` generates wrapper classes dynamically, IDEs can't infer their method signatures. To restore full
|
|
213
|
+
autocomplete and type checking, `.pyi` stub files are auto-generated.
|
|
214
|
+
|
|
215
|
+
**Regenerate stubs locally:**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv run python generate_stubs.py
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Stubs are generated automatically during the GitHub Actions release workflow, so you don't need to commit them — they're
|
|
222
|
+
in `.gitignore`.
|
|
223
|
+
|
|
224
|
+
### Adding a New Route
|
|
225
|
+
|
|
226
|
+
1. Create `src/py_librenms/routes/myroute.py` with an async class and `MyRouteSync = synchronizer.wrap(...)` at the
|
|
227
|
+
bottom.
|
|
228
|
+
2. Create `src/py_librenms/models/myroute.py` with Pydantic response models.
|
|
229
|
+
3. Add exports to `src/py_librenms/models/__init__.py`.
|
|
230
|
+
4. Add exports to `src/py_librenms/routes/__init__.py`.
|
|
231
|
+
5. Wire up the route in `src/py_librenms/client.py` (both sync and async clients).
|
|
232
|
+
6. Run `uv run python generate_stubs.py`.
|
|
233
|
+
7. Add tests in `tests/unit/routes/test_myroute.py` and `tests/unit/models/test_myroute.py`.
|
|
234
|
+
|
|
235
|
+
### Commit Convention
|
|
236
|
+
|
|
237
|
+
This project enforces [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) via
|
|
238
|
+
[commitizen](https://commitizen-tools.github.io/commitizen/). A git hook validates every commit message automatically.
|
|
239
|
+
|
|
240
|
+
**Setup the hook (once per clone):**
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git config core.hooksPath .githooks
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Format:**
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
type(scope)?: description
|
|
250
|
+
|
|
251
|
+
[optional body]
|
|
252
|
+
[optional footer]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Allowed types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, `bump`
|
|
256
|
+
|
|
257
|
+
**Examples:**
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
feat(routing): add OSPFv3 port listing
|
|
261
|
+
fix: handle empty response from list_devices
|
|
262
|
+
docs: add upstream tracking section to README
|
|
263
|
+
test: add functional tests for switching routes
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Upstream API Tracking
|
|
267
|
+
|
|
268
|
+
This project tracks which LibreNMS release tag the route implementations are based on. The pinned version is stored in
|
|
269
|
+
`upstream_tracking.toml`.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Check if upstream has a newer release
|
|
273
|
+
python check_upstream.py
|
|
274
|
+
|
|
275
|
+
# See which API doc files changed
|
|
276
|
+
python check_upstream.py --diff
|
|
277
|
+
|
|
278
|
+
# See full unified diffs of changed docs
|
|
279
|
+
python check_upstream.py --full
|
|
280
|
+
|
|
281
|
+
# Compare against a specific tag instead of latest
|
|
282
|
+
python check_upstream.py --diff --tag 26.6.0
|
|
283
|
+
|
|
284
|
+
# Bump the pinned tag after reviewing changes
|
|
285
|
+
python check_upstream.py --bump
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Project Structure
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
libreclient/
|
|
292
|
+
├── src/py_librenms/
|
|
293
|
+
│ ├── __init__.py # Public API exports
|
|
294
|
+
│ ├── client.py # LibreClientSync & LibreClientAsync
|
|
295
|
+
│ ├── config.py # Pydantic-settings configuration
|
|
296
|
+
│ ├── _base_client.py # Shared HTTP transport logic
|
|
297
|
+
│ ├── models/ # Pydantic response models
|
|
298
|
+
│ └── routes/ # Route namespaces (async + sync wrappers)
|
|
299
|
+
│ ├── _types.py # ClientProtocol & utilities
|
|
300
|
+
│ ├── _synchronicity.py # Shared Synchronizer instance
|
|
301
|
+
│ ├── alerts.py # Example route implementation
|
|
302
|
+
│ └── ...
|
|
303
|
+
├── tests/
|
|
304
|
+
│ ├── unit/
|
|
305
|
+
│ │ ├── models/ # Model validation tests
|
|
306
|
+
│ │ └── routes/ # Route logic tests (MockClient)
|
|
307
|
+
│ └── functional/ # Live API tests (requires .env)
|
|
308
|
+
├── check_upstream.py # Detect upstream API doc changes
|
|
309
|
+
├── upstream_tracking.toml # Pinned LibreNMS release tag
|
|
310
|
+
├── generate_stubs.py # .pyi stub generator
|
|
311
|
+
├── pyproject.toml
|
|
312
|
+
├── CHANGELOG.md
|
|
313
|
+
└── LICENSE
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
[MIT](LICENSE)
|
|
319
|
+
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# libreclient
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
|
|
6
|
+
Async and sync Python client for the [LibreNMS](https://www.librenms.org/) API.
|
|
7
|
+
|
|
8
|
+
- **Dual interface** — use `LibreClientAsync` for async/await or `LibreClientSync` for traditional blocking calls.
|
|
9
|
+
- **Typed responses** — all endpoints return Pydantic models with full IDE autocomplete.
|
|
10
|
+
- **Environment-driven config** — configure via `LIBRENMS_URL` and `LIBRENMS_TOKEN` env vars or pass values directly.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install libreclient
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv add libreclient
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Synchronous
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from libreclient import LibreClientSync
|
|
30
|
+
|
|
31
|
+
client = LibreClientSync(url="https://librenms.example.com", token="your-api-token")
|
|
32
|
+
|
|
33
|
+
# List all devices
|
|
34
|
+
response = client.devices.list_devices()
|
|
35
|
+
for device in response.devices:
|
|
36
|
+
print(device["hostname"])
|
|
37
|
+
|
|
38
|
+
# Get a specific alert
|
|
39
|
+
alert = client.alerts.get_alert(42)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Asynchronous
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import asyncio
|
|
46
|
+
from libreclient import LibreClientAsync
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def main():
|
|
50
|
+
client = LibreClientAsync(url="https://librenms.example.com", token="your-api-token")
|
|
51
|
+
|
|
52
|
+
response = await client.devices.list_devices()
|
|
53
|
+
for device in response.devices:
|
|
54
|
+
print(device["hostname"])
|
|
55
|
+
|
|
56
|
+
await client.close()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
asyncio.run(main())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Context Manager
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# Sync
|
|
66
|
+
with LibreClientSync(url="https://librenms.example.com", token="your-api-token") as client:
|
|
67
|
+
print(client.system.ping())
|
|
68
|
+
|
|
69
|
+
# Async
|
|
70
|
+
async with LibreClientAsync(url="https://librenms.example.com", token="your-api-token") as client:
|
|
71
|
+
print(await client.system.ping())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Configuration is handled by [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). You can
|
|
77
|
+
pass values directly or set environment variables:
|
|
78
|
+
|
|
79
|
+
| Env Variable | Description | Default |
|
|
80
|
+
|------------------------|------------------------------------|--------------|
|
|
81
|
+
| `LIBRENMS_URL` | Base URL of your LibreNMS instance | *(required)* |
|
|
82
|
+
| `LIBRENMS_TOKEN` | API token (`X-Auth-Token`) | *(required)* |
|
|
83
|
+
| `LIBRENMS_VERIFY_SSL` | Verify TLS certificates | `true` |
|
|
84
|
+
| `LIBRENMS_API_VERSION` | API version path segment | `v0` |
|
|
85
|
+
|
|
86
|
+
A `.env` file in your working directory is also supported. Copy the included sample to get started:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cp sample.env .env
|
|
90
|
+
# Edit .env with your LibreNMS URL and API token
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Available Route Namespaces
|
|
94
|
+
|
|
95
|
+
All route namespaces are accessible as properties on the client:
|
|
96
|
+
|
|
97
|
+
| Property | Description |
|
|
98
|
+
|------------------------|--------------------------------------------|
|
|
99
|
+
| `client.alerts` | Alert management and alert rules/templates |
|
|
100
|
+
| `client.arp` | ARP table lookups |
|
|
101
|
+
| `client.bills` | Billing data and graphs |
|
|
102
|
+
| `client.device_groups` | Device group management |
|
|
103
|
+
| `client.devices` | Device CRUD, discovery, components, graphs |
|
|
104
|
+
| `client.index` | List available API endpoints |
|
|
105
|
+
| `client.inventory` | Hardware inventory |
|
|
106
|
+
| `client.locations` | Location management |
|
|
107
|
+
| `client.logs` | Event, syslog, alert, and auth logs |
|
|
108
|
+
| `client.poller_groups` | Poller group info |
|
|
109
|
+
| `client.pollers` | Poller status |
|
|
110
|
+
| `client.port_groups` | Port group management |
|
|
111
|
+
| `client.port_security` | Port security (802.1X/MAB) |
|
|
112
|
+
| `client.ports` | Port info, search, and descriptions |
|
|
113
|
+
| `client.routing` | BGP, OSPF, VRF, MPLS, IPsec |
|
|
114
|
+
| `client.services` | Service monitoring |
|
|
115
|
+
| `client.switching` | VLANs, links, FDB, NAC |
|
|
116
|
+
| `client.system` | Ping and system info |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
### Prerequisites
|
|
123
|
+
|
|
124
|
+
- Python 3.12+
|
|
125
|
+
- [uv](https://docs.astral.sh/uv/) (package manager)
|
|
126
|
+
|
|
127
|
+
### Setup
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/jjeff07/libreclient.git
|
|
131
|
+
cd libreclient
|
|
132
|
+
uv sync
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Running Tests
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Unit tests
|
|
139
|
+
uv run pytest tests/unit
|
|
140
|
+
|
|
141
|
+
# Functional tests (requires .env with LIBRENMS_URL and LIBRENMS_TOKEN)
|
|
142
|
+
uv run pytest tests/functional
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Linting & Formatting
|
|
146
|
+
|
|
147
|
+
This project uses [Ruff](https://docs.astral.sh/ruff/) for both linting and formatting:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Check for lint issues
|
|
151
|
+
uv run ruff check
|
|
152
|
+
|
|
153
|
+
# Auto-fix lint issues
|
|
154
|
+
uv run ruff check --fix
|
|
155
|
+
|
|
156
|
+
# Format code
|
|
157
|
+
uv run ruff format
|
|
158
|
+
|
|
159
|
+
# Check formatting without changing files
|
|
160
|
+
uv run ruff format --check
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Complexity Checks
|
|
164
|
+
|
|
165
|
+
[complexipy](https://github.com/rohaquinlop/complexipy) is used to enforce a maximum cognitive complexity of 15 per
|
|
166
|
+
function:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
uv run complexipy .
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Results are output to `complexipy-results.json`. Any function exceeding the threshold will cause the check to fail.
|
|
173
|
+
|
|
174
|
+
### Architecture
|
|
175
|
+
|
|
176
|
+
The project uses a single-implementation pattern: each route is written **once** as an async class.
|
|
177
|
+
The [synchronicity](https://github.com/modal-com/synchronicity) library then wraps each async class to produce a
|
|
178
|
+
synchronous counterpart at runtime.
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
src/py_librenms/routes/alerts.py
|
|
182
|
+
├── class Alerts ← async implementation (the only code you write)
|
|
183
|
+
└── AlertsSync = synchronizer.wrap(Alerts, ...) ← sync wrapper (auto-generated at import)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
This means:
|
|
187
|
+
|
|
188
|
+
- You only maintain one implementation per route.
|
|
189
|
+
- Both `LibreClientAsync` and `LibreClientSync` share the same logic.
|
|
190
|
+
- No code duplication between sync and async interfaces.
|
|
191
|
+
|
|
192
|
+
### Type Stubs
|
|
193
|
+
|
|
194
|
+
Because `synchronicity` generates wrapper classes dynamically, IDEs can't infer their method signatures. To restore full
|
|
195
|
+
autocomplete and type checking, `.pyi` stub files are auto-generated.
|
|
196
|
+
|
|
197
|
+
**Regenerate stubs locally:**
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
uv run python generate_stubs.py
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Stubs are generated automatically during the GitHub Actions release workflow, so you don't need to commit them — they're
|
|
204
|
+
in `.gitignore`.
|
|
205
|
+
|
|
206
|
+
### Adding a New Route
|
|
207
|
+
|
|
208
|
+
1. Create `src/py_librenms/routes/myroute.py` with an async class and `MyRouteSync = synchronizer.wrap(...)` at the
|
|
209
|
+
bottom.
|
|
210
|
+
2. Create `src/py_librenms/models/myroute.py` with Pydantic response models.
|
|
211
|
+
3. Add exports to `src/py_librenms/models/__init__.py`.
|
|
212
|
+
4. Add exports to `src/py_librenms/routes/__init__.py`.
|
|
213
|
+
5. Wire up the route in `src/py_librenms/client.py` (both sync and async clients).
|
|
214
|
+
6. Run `uv run python generate_stubs.py`.
|
|
215
|
+
7. Add tests in `tests/unit/routes/test_myroute.py` and `tests/unit/models/test_myroute.py`.
|
|
216
|
+
|
|
217
|
+
### Commit Convention
|
|
218
|
+
|
|
219
|
+
This project enforces [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) via
|
|
220
|
+
[commitizen](https://commitizen-tools.github.io/commitizen/). A git hook validates every commit message automatically.
|
|
221
|
+
|
|
222
|
+
**Setup the hook (once per clone):**
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git config core.hooksPath .githooks
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Format:**
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
type(scope)?: description
|
|
232
|
+
|
|
233
|
+
[optional body]
|
|
234
|
+
[optional footer]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Allowed types:** `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, `bump`
|
|
238
|
+
|
|
239
|
+
**Examples:**
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
feat(routing): add OSPFv3 port listing
|
|
243
|
+
fix: handle empty response from list_devices
|
|
244
|
+
docs: add upstream tracking section to README
|
|
245
|
+
test: add functional tests for switching routes
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Upstream API Tracking
|
|
249
|
+
|
|
250
|
+
This project tracks which LibreNMS release tag the route implementations are based on. The pinned version is stored in
|
|
251
|
+
`upstream_tracking.toml`.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Check if upstream has a newer release
|
|
255
|
+
python check_upstream.py
|
|
256
|
+
|
|
257
|
+
# See which API doc files changed
|
|
258
|
+
python check_upstream.py --diff
|
|
259
|
+
|
|
260
|
+
# See full unified diffs of changed docs
|
|
261
|
+
python check_upstream.py --full
|
|
262
|
+
|
|
263
|
+
# Compare against a specific tag instead of latest
|
|
264
|
+
python check_upstream.py --diff --tag 26.6.0
|
|
265
|
+
|
|
266
|
+
# Bump the pinned tag after reviewing changes
|
|
267
|
+
python check_upstream.py --bump
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Project Structure
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
libreclient/
|
|
274
|
+
├── src/py_librenms/
|
|
275
|
+
│ ├── __init__.py # Public API exports
|
|
276
|
+
│ ├── client.py # LibreClientSync & LibreClientAsync
|
|
277
|
+
│ ├── config.py # Pydantic-settings configuration
|
|
278
|
+
│ ├── _base_client.py # Shared HTTP transport logic
|
|
279
|
+
│ ├── models/ # Pydantic response models
|
|
280
|
+
│ └── routes/ # Route namespaces (async + sync wrappers)
|
|
281
|
+
│ ├── _types.py # ClientProtocol & utilities
|
|
282
|
+
│ ├── _synchronicity.py # Shared Synchronizer instance
|
|
283
|
+
│ ├── alerts.py # Example route implementation
|
|
284
|
+
│ └── ...
|
|
285
|
+
├── tests/
|
|
286
|
+
│ ├── unit/
|
|
287
|
+
│ │ ├── models/ # Model validation tests
|
|
288
|
+
│ │ └── routes/ # Route logic tests (MockClient)
|
|
289
|
+
│ └── functional/ # Live API tests (requires .env)
|
|
290
|
+
├── check_upstream.py # Detect upstream API doc changes
|
|
291
|
+
├── upstream_tracking.toml # Pinned LibreNMS release tag
|
|
292
|
+
├── generate_stubs.py # .pyi stub generator
|
|
293
|
+
├── pyproject.toml
|
|
294
|
+
├── CHANGELOG.md
|
|
295
|
+
└── LICENSE
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
[MIT](LICENSE)
|
|
301
|
+
|