Skip to content

Commit 940c154

Browse files
committed
Merge branch 'main' into release/current
2 parents 3e7856d + fe4ec53 commit 940c154

File tree

6 files changed

+104
-22
lines changed

6 files changed

+104
-22
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@ dmypy.json
133133

134134
# Cython debug symbols
135135
cython_debug/
136+
137+
# testing
138+
test.py

pyobas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "1.18.12"
2+
__version__ = "1.18.16"
33

44
from pyobas._version import ( # noqa: F401
55
__author__,

pyobas/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
__email__ = "[email protected]"
44
__license__ = "Apache 2.0"
55
__title__ = "python-openbas"
6-
__version__ = "1.18.12"
6+
__version__ = "1.18.16"

pyobas/apis/endpoint.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pyobas import exceptions as exc
44
from pyobas.base import RESTManager, RESTObject
5+
from pyobas.utils import RequiredOptional
56

67

78
class Endpoint(RESTObject):
@@ -11,9 +12,27 @@ class Endpoint(RESTObject):
1112
class EndpointManager(RESTManager):
1213
_path = "/endpoints"
1314
_obj_cls = Endpoint
15+
_create_attrs = RequiredOptional(
16+
required=(
17+
"endpoint_hostname",
18+
"endpoint_platform",
19+
"endpoint_arch",
20+
),
21+
optional=(
22+
"endpoint_mac_addresses",
23+
"endpoint_ips",
24+
"asset_external_reference",
25+
),
26+
)
1427

1528
@exc.on_http_error(exc.OpenBASUpdateError)
1629
def get(self, asset_id: str, **kwargs: Any) -> Dict[str, Any]:
1730
path = f"{self.path}/" + asset_id
1831
result = self.openbas.http_get(path, **kwargs)
1932
return result
33+
34+
@exc.on_http_error(exc.OpenBASUpdateError)
35+
def upsert(self, endpoint: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]:
36+
path = f"{self.path}/agentless/upsert"
37+
result = self.openbas.http_post(path, post_data=endpoint, **kwargs)
38+
return result

pyobas/client.py

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

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