rackfish 1.0.1__py3-none-any.whl → 1.0.3__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.
- rackfish/__init__.py +1 -1
- rackfish/client.py +56 -12
- {rackfish-1.0.1.dist-info → rackfish-1.0.3.dist-info}/METADATA +9 -34
- rackfish-1.0.3.dist-info/RECORD +7 -0
- rackfish-1.0.1.dist-info/RECORD +0 -7
- {rackfish-1.0.1.dist-info → rackfish-1.0.3.dist-info}/WHEEL +0 -0
- {rackfish-1.0.1.dist-info → rackfish-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {rackfish-1.0.1.dist-info → rackfish-1.0.3.dist-info}/top_level.txt +0 -0
rackfish/__init__.py
CHANGED
rackfish/client.py
CHANGED
@@ -7,6 +7,7 @@ import threading
|
|
7
7
|
from typing import Any, ClassVar, Iterable
|
8
8
|
|
9
9
|
import requests
|
10
|
+
import urllib3
|
10
11
|
|
11
12
|
# HTTP status codes
|
12
13
|
HTTP_OK = 200
|
@@ -74,11 +75,6 @@ class RedfishClient:
|
|
74
75
|
default_headers: dict[str, str] | None = None,
|
75
76
|
):
|
76
77
|
base = base_url.rstrip("/")
|
77
|
-
# if not base.endswith("/redfish/v1"):
|
78
|
-
# if base.endswith("/redfish"):
|
79
|
-
# base = base + "/v1"
|
80
|
-
# else:
|
81
|
-
# base = base + "/redfish/v1"
|
82
78
|
|
83
79
|
self.base_url = base
|
84
80
|
self.username = username
|
@@ -88,6 +84,11 @@ class RedfishClient:
|
|
88
84
|
|
89
85
|
self._http = requests.Session()
|
90
86
|
self._http.verify = verify_ssl
|
87
|
+
|
88
|
+
# Suppress SSL warnings when verify_ssl is disabled
|
89
|
+
if not verify_ssl:
|
90
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
91
|
+
|
91
92
|
self._http.headers.update(default_headers or {"Accept": "application/json"})
|
92
93
|
if username and password and not use_session:
|
93
94
|
self._http.auth = (username, password)
|
@@ -156,9 +157,12 @@ class RedfishClient:
|
|
156
157
|
return None
|
157
158
|
return None
|
158
159
|
|
159
|
-
def patch(self, path: str, data: dict[str, Any]) -> None:
|
160
|
+
def patch(self, path: str, data: dict[str, Any], etag: str | None = None) -> None:
|
160
161
|
url = _safe_join(self.base_url, path) if not path.startswith("http") else path
|
161
|
-
|
162
|
+
headers = {}
|
163
|
+
if etag:
|
164
|
+
headers["If-Match"] = etag
|
165
|
+
resp = self._http.patch(url, json=data, headers=headers, timeout=self.timeout)
|
162
166
|
if resp.status_code not in (200, 204):
|
163
167
|
raise RedfishError(f"PATCH {url} -> {resp.status_code} {resp.text}")
|
164
168
|
|
@@ -191,7 +195,27 @@ class RedfishClient:
|
|
191
195
|
|
192
196
|
def __getattr__(self, name: str) -> Any:
|
193
197
|
# Proxy unknown attributes to root (e.g., client.Systems)
|
194
|
-
|
198
|
+
# If attribute doesn't exist but plural form exists with single member,
|
199
|
+
# return that member directly (e.g., client.System -> client.Systems[0])
|
200
|
+
try:
|
201
|
+
return getattr(self.root, name)
|
202
|
+
except AttributeError as exc:
|
203
|
+
# Try plural form for singular access convenience
|
204
|
+
plural_name = name + "s"
|
205
|
+
try:
|
206
|
+
collection = getattr(self.root, plural_name)
|
207
|
+
if (
|
208
|
+
hasattr(collection, "__len__")
|
209
|
+
and hasattr(collection, "__iter__")
|
210
|
+
and len(collection) == 1
|
211
|
+
):
|
212
|
+
return next(iter(collection))
|
213
|
+
except (AttributeError, TypeError):
|
214
|
+
pass
|
215
|
+
# Re-raise original error
|
216
|
+
raise AttributeError(
|
217
|
+
f"'{type(self).__name__}' object has no attribute '{name}'"
|
218
|
+
) from exc
|
195
219
|
|
196
220
|
|
197
221
|
# ---------------------------
|
@@ -383,11 +407,28 @@ class RedfishResource:
|
|
383
407
|
self._ensure_fetched()
|
384
408
|
try:
|
385
409
|
return object.__getattribute__(self, name)
|
386
|
-
except AttributeError:
|
410
|
+
except AttributeError as exc:
|
387
411
|
# Not an attribute; maybe a JSON property with non-identifier key
|
388
412
|
if name in self._raw:
|
389
413
|
return self._raw[name]
|
390
|
-
|
414
|
+
|
415
|
+
# Try plural form for singular access convenience
|
416
|
+
# e.g., resource.Chassis -> resource.Chassis[0] if len == 1
|
417
|
+
plural_name = name + "s"
|
418
|
+
try:
|
419
|
+
collection = object.__getattribute__(self, plural_name)
|
420
|
+
if (
|
421
|
+
hasattr(collection, "__len__")
|
422
|
+
and hasattr(collection, "__iter__")
|
423
|
+
and len(collection) == 1
|
424
|
+
):
|
425
|
+
return next(iter(collection))
|
426
|
+
except (AttributeError, TypeError):
|
427
|
+
pass
|
428
|
+
|
429
|
+
raise AttributeError(
|
430
|
+
f"'{type(self).__name__}' object has no attribute '{name}'"
|
431
|
+
) from exc
|
391
432
|
|
392
433
|
def __setattr__(self, name: str, value: Any) -> None:
|
393
434
|
# Allow setting simple existing properties via PATCH
|
@@ -400,7 +441,8 @@ class RedfishResource:
|
|
400
441
|
|
401
442
|
if name in self._raw and not isinstance(self._raw[name], (dict, list)):
|
402
443
|
# PATCH only simple properties by default; complex updates via .patch()
|
403
|
-
self.
|
444
|
+
etag = self._raw.get("@odata.etag")
|
445
|
+
self._client.patch(self._path, {name: value}, etag=etag)
|
404
446
|
self._raw[name] = value
|
405
447
|
object.__setattr__(self, name, value)
|
406
448
|
else:
|
@@ -458,7 +500,9 @@ class RedfishResource:
|
|
458
500
|
def patch(self, updates: dict[str, Any]) -> None:
|
459
501
|
if not self._path:
|
460
502
|
raise RedfishError("PATCH requires a resource path")
|
461
|
-
self.
|
503
|
+
self._ensure_fetched()
|
504
|
+
etag = self._raw.get("@odata.etag")
|
505
|
+
self._client.patch(self._path, updates, etag=etag)
|
462
506
|
self.refresh()
|
463
507
|
|
464
508
|
def delete(self) -> None:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: rackfish
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.3
|
4
4
|
Summary: A lightweight, dynamic Python client for Redfish BMC APIs
|
5
5
|
Author-email: Dmitrii Frolov <thefrolov@mts.ru>
|
6
6
|
License: MIT
|
@@ -58,17 +58,6 @@ A lightweight, dynamic Python client for interacting with Redfish BMC (Baseboard
|
|
58
58
|
- 📚 **Collection Support** - Iterate Redfish collections naturally
|
59
59
|
- 🔐 **Flexible Auth** - Session tokens or Basic authentication
|
60
60
|
|
61
|
-
## Features
|
62
|
-
|
63
|
-
- **Zero Dependencies** (except `requests`) - Minimal footprint
|
64
|
-
- **Lazy Loading** - Resources fetched on-demand for performance
|
65
|
-
- **Dynamic Attributes** - JSON properties become Python attributes
|
66
|
-
- **OEM Surfacing** - Vendor extensions automatically accessible
|
67
|
-
- **Links Surfacing** - Related resources directly navigable
|
68
|
-
- **Action Validation** - Parameter validation using ActionInfo schemas
|
69
|
-
- **Collection Support** - Iterate Redfish collections naturally
|
70
|
-
- **Session & Basic Auth** - Flexible authentication options
|
71
|
-
|
72
61
|
## Installation
|
73
62
|
|
74
63
|
### From PyPI (recommended)
|
@@ -101,8 +90,11 @@ client = RedfishClient("https://bmc.example.com", "admin", "password",
|
|
101
90
|
use_session=True, verify_ssl=False)
|
102
91
|
root = client.connect()
|
103
92
|
|
104
|
-
# Power control
|
105
|
-
system = next(iter(client.Systems))
|
93
|
+
# Power control - multiple ways to access systems
|
94
|
+
system = next(iter(client.Systems)) # Traditional iteration
|
95
|
+
system = client.Systems.Members[0] # Direct member access
|
96
|
+
system = client.System # Singular form (if only one member!)
|
97
|
+
|
106
98
|
system.Reset(ResetType="GracefulRestart")
|
107
99
|
|
108
100
|
# Access OEM properties (auto-surfaced)
|
@@ -166,7 +158,8 @@ for temp in chassis.Thermal.Temperatures:
|
|
166
158
|
print(f"{temp.Name}: {temp.ReadingCelsius}°C")
|
167
159
|
```
|
168
160
|
|
169
|
-
See [EXAMPLES.md](EXAMPLES.md) for 100+ more examples covering:
|
161
|
+
See [docs/EXAMPLES.md](docs/EXAMPLES.md) for 100+ more examples covering:
|
162
|
+
|
170
163
|
- BIOS configuration
|
171
164
|
- Certificate management
|
172
165
|
- Virtual media (KVM)
|
@@ -175,24 +168,6 @@ See [EXAMPLES.md](EXAMPLES.md) for 100+ more examples covering:
|
|
175
168
|
- SEL/log collection
|
176
169
|
- And much more...
|
177
170
|
|
178
|
-
## Testing
|
179
|
-
|
180
|
-
Run the test suite:
|
181
|
-
|
182
|
-
```bash
|
183
|
-
# Install with dev dependencies
|
184
|
-
pip install -e ".[dev]"
|
185
|
-
|
186
|
-
# Run all tests
|
187
|
-
pytest tests/
|
188
|
-
|
189
|
-
# Run with coverage
|
190
|
-
pytest --cov=rackfish tests/
|
191
|
-
|
192
|
-
# Run specific test file
|
193
|
-
pytest tests/test_common_usage.py
|
194
|
-
```
|
195
|
-
|
196
171
|
## Project Structure
|
197
172
|
|
198
173
|
```
|
@@ -356,7 +331,7 @@ See LICENSE file.
|
|
356
331
|
|
357
332
|
## Version
|
358
333
|
|
359
|
-
Current version: 1.0.
|
334
|
+
Current version: 1.0.3
|
360
335
|
|
361
336
|
## Requirements
|
362
337
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
rackfish/__init__.py,sha256=hYKyM58DkDSL2ReP0CI18RT-TuBcYTmDZp5gepUfUGk,866
|
2
|
+
rackfish/client.py,sha256=aHtQikXEEsI_jjq24-LE8mR2sCasdAlhbB2zRP97200,25048
|
3
|
+
rackfish-1.0.3.dist-info/licenses/LICENSE,sha256=RqhA3IjOD-4eEnG-vVcLLU1vdpVCUyCQpytsCP3P0Q0,1071
|
4
|
+
rackfish-1.0.3.dist-info/METADATA,sha256=CnnyEOogYJYjOyRAnBpcGw21jnEO66EXhIihdDuRpeE,10645
|
5
|
+
rackfish-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
+
rackfish-1.0.3.dist-info/top_level.txt,sha256=71o_wqNuMF4jtlGbXuI2-Epzd2a9oSizMmdcG3Sw_Mo,9
|
7
|
+
rackfish-1.0.3.dist-info/RECORD,,
|
rackfish-1.0.1.dist-info/RECORD
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
rackfish/__init__.py,sha256=gXLovtZSL_Od0gBj5huedVcJ30ufDLRwYJxTV7eE7Q0,866
|
2
|
-
rackfish/client.py,sha256=4eOyfQtqo9kbvzxiV2DqyeM6t4fgAnKbj3odpiS9dmY,23225
|
3
|
-
rackfish-1.0.1.dist-info/licenses/LICENSE,sha256=RqhA3IjOD-4eEnG-vVcLLU1vdpVCUyCQpytsCP3P0Q0,1071
|
4
|
-
rackfish-1.0.1.dist-info/METADATA,sha256=55T2anIwPlDVEw1vnYT5fuAs-ZzVN4Vs6F4tEdwq9qA,11215
|
5
|
-
rackfish-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
-
rackfish-1.0.1.dist-info/top_level.txt,sha256=71o_wqNuMF4jtlGbXuI2-Epzd2a9oSizMmdcG3Sw_Mo,9
|
7
|
-
rackfish-1.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|