Skip to content

Commit f70a39d

Browse files
committed
[client] Enhance logging and implement endpoint upsert
1 parent 93a8281 commit f70a39d

File tree

3 files changed

+89
-25
lines changed

3 files changed

+89
-25
lines changed

pyobas/apis/endpoint.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pyobas.base import RESTManager, RESTObject
55
from pyobas.utils import RequiredOptional
66

7+
78
class Endpoint(RESTObject):
89
_id_attr = "asset_id"
910

@@ -12,7 +13,12 @@ class EndpointManager(RESTManager):
1213
_path = "/endpoints"
1314
_obj_cls = Endpoint
1415
_create_attrs = RequiredOptional(
15-
required=("endpoint_hostname", "endpoint_ips", "endpoint_platform", "endpoint_arch"),
16+
required=(
17+
"endpoint_hostname",
18+
"endpoint_ips",
19+
"endpoint_platform",
20+
"endpoint_arch",
21+
),
1622
optional=(
1723
"endpoint_mac_addresses",
1824
"asset_external_reference",
@@ -26,9 +32,7 @@ def get(self, asset_id: str, **kwargs: Any) -> Dict[str, Any]:
2632
return result
2733

2834
@exc.on_http_error(exc.OpenBASUpdateError)
29-
def upsert(
30-
self, endpoint: Dict[str, Any], **kwargs: Any
31-
) -> Dict[str, Any]:
35+
def upsert(self, endpoint: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
3236
path = f"{self.path}/agentless/upsert"
3337
result = self.openbas.http_post(path, post_data=endpoint, **kwargs)
34-
return result
38+
return result

pyobas/client.py

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ def http_request(
196196
stream=streamed,
197197
**opts,
198198
)
199-
200199
self._check_redirects(result.response)
201200

202201
if 200 <= result.status_code < 300:
@@ -224,25 +223,8 @@ def http_request(
224223
error_json = result.json()
225224
# Common fields
226225
if isinstance(error_json, dict):
227-
# First priority: look for a 'message' field (most specific)
228-
if "message" in error_json:
229-
error_message = error_json.get("message")
230-
elif "execution_message" in error_json:
231-
error_message = error_json.get("execution_message")
232-
elif "error" in error_json:
233-
err = error_json.get("error")
234-
if isinstance(err, dict) and "message" in err:
235-
error_message = err.get("message")
236-
elif err and err not in [
237-
"Internal Server Error",
238-
"Bad Request",
239-
"Not Found",
240-
"Unauthorized",
241-
"Forbidden",
242-
]:
243-
# Only use 'error' field if it's not a generic HTTP status
244-
error_message = str(err)
245-
elif "errors" in error_json:
226+
# Check for nested validation errors first (more specific)
227+
if "errors" in error_json:
246228
errs = error_json.get("errors")
247229
if isinstance(errs, list) and errs:
248230
# Join any messages in the list
@@ -253,6 +235,52 @@ def http_request(
253235
else:
254236
messages.append(str(item))
255237
error_message = "; ".join(messages)
238+
elif isinstance(errs, dict):
239+
# Handle nested validation errors from OpenBAS
240+
if "children" in errs:
241+
# This is a validation error structure
242+
validation_errors = []
243+
children = errs.get("children", {})
244+
for field, field_errors in children.items():
245+
if (
246+
isinstance(field_errors, dict)
247+
and "errors" in field_errors
248+
):
249+
field_error_list = field_errors.get(
250+
"errors", []
251+
)
252+
if field_error_list:
253+
for err_msg in field_error_list:
254+
validation_errors.append(
255+
f"{field}: {err_msg}"
256+
)
257+
if validation_errors:
258+
base_msg = error_json.get(
259+
"message", "Validation Failed"
260+
)
261+
error_message = f"{base_msg}: {'; '.join(validation_errors)}"
262+
elif isinstance(errs, str):
263+
error_message = errs
264+
265+
# If no error message from errors field, check other fields
266+
if not error_message:
267+
if "message" in error_json:
268+
error_message = error_json.get("message")
269+
elif "execution_message" in error_json:
270+
error_message = error_json.get("execution_message")
271+
elif "error" in error_json:
272+
err = error_json.get("error")
273+
if isinstance(err, dict) and "message" in err:
274+
error_message = err.get("message")
275+
elif err and err not in [
276+
"Internal Server Error",
277+
"Bad Request",
278+
"Not Found",
279+
"Unauthorized",
280+
"Forbidden",
281+
]:
282+
# Only use 'error' field if it's not a generic HTTP status
283+
error_message = str(err)
256284
elif isinstance(error_json, str):
257285
error_message = error_json
258286
# Fallback to serialized json if we still have nothing

pyobas/exceptions.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __str__(self) -> str:
3939
"Service Unavailable",
4040
"Gateway Timeout",
4141
"Unknown error",
42+
"Validation Failed",
4243
)
4344

4445
# Only try to extract from response body if message is truly generic
@@ -78,6 +79,37 @@ def __str__(self) -> str:
7879
else:
7980
parts.append(str(item))
8081
extracted_msg = "; ".join(parts)
82+
elif isinstance(errs, dict):
83+
# Handle nested validation errors structure
84+
if "children" in errs:
85+
validation_errors = []
86+
children = errs.get("children", {})
87+
for field, field_errors in children.items():
88+
if (
89+
isinstance(field_errors, dict)
90+
and "errors" in field_errors
91+
):
92+
field_error_list = field_errors.get(
93+
"errors", []
94+
)
95+
if field_error_list:
96+
for err_msg in field_error_list:
97+
validation_errors.append(
98+
f"{field}: {err_msg}"
99+
)
100+
if validation_errors:
101+
base_msg = data.get("message", "Validation Failed")
102+
extracted_msg = (
103+
f"{base_msg}: {'; '.join(validation_errors)}"
104+
)
105+
else:
106+
# Try to get any string representation
107+
parts = []
108+
for key, value in errs.items():
109+
if value:
110+
parts.append(f"{key}: {value}")
111+
if parts:
112+
extracted_msg = "; ".join(parts)
81113
elif isinstance(errs, str):
82114
extracted_msg = errs
83115

0 commit comments

Comments
 (0)