Skip to content

Commit 4e7af7f

Browse files
committed
fix errors
Signed-off-by: Serena Ruan <[email protected]>
1 parent 49eb17b commit 4e7af7f

File tree

5 files changed

+114
-2
lines changed

5 files changed

+114
-2
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Release v0.71.0
44

55
### New Features and Improvements
6+
* Add a new `_ProtobufErrorDeserializer` for handling Protobuf response errors.
67

78
### Bug Fixes
89

databricks/sdk/errors/deserializer.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,22 @@ def deserialize_error(self, response: requests.Response, response_body: bytes) -
117117
}
118118
logging.debug("_HtmlErrorParser: no <pre> tag found in error response")
119119
return None
120+
121+
class _ProtobufErrorDeserializer(_ErrorDeserializer):
122+
"""
123+
Parses errors from the Databricks REST API in Protobuf format.
124+
"""
125+
126+
def deserialize_error(self, response: requests.Response, response_body: bytes) -> Optional[dict]:
127+
try:
128+
from google.rpc import status_pb2
129+
130+
status = status_pb2.Status()
131+
status.ParseFromString(response_body)
132+
return {
133+
"message": status.message,
134+
"error_code": response.status_code,
135+
}
136+
except Exception as e:
137+
logging.debug("_ProtobufErrorParser: unable to parse response as Protobuf", exc_info=e)
138+
return None

databricks/sdk/errors/parser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .customizer import _ErrorCustomizer, _RetryAfterCustomizer
99
from .deserializer import (_EmptyDeserializer, _ErrorDeserializer,
1010
_HtmlErrorDeserializer, _StandardErrorDeserializer,
11-
_StringErrorDeserializer)
11+
_StringErrorDeserializer, _ProtobufErrorDeserializer)
1212
from .mapper import _error_mapper
1313
from .private_link import (_get_private_link_validation_error,
1414
_is_private_link_redirect)
@@ -21,6 +21,7 @@
2121
_StandardErrorDeserializer(),
2222
_StringErrorDeserializer(),
2323
_HtmlErrorDeserializer(),
24+
_ProtobufErrorDeserializer(),
2425
]
2526

2627
# A list of _ErrorCustomizers that are applied to the error arguments after they are parsed. Customizers can modify the

databricks/sdk/logger/round_trip_logger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def generate(self) -> str:
4444
for k, v in request.headers.items():
4545
sb.append(f"> * {k}: {self._only_n_bytes(v, self._debug_truncate_bytes)}")
4646
if request.body:
47-
sb.append("> [raw stream]" if self._raw else self._redacted_dump("> ", request.body))
47+
sb.append("> [raw stream]" if self._raw else self._redacted_dump("> ", str(request.body)))
4848
sb.append(f"< {self._response.status_code} {self._response.reason}")
4949
if self._raw and self._response.headers.get("Content-Type", None) != "application/json":
5050
# Raw streams with `Transfer-Encoding: chunked` do not have `Content-Type` header

tests/test_errors.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from databricks.sdk import errors
1010
from databricks.sdk.errors import details
11+
from google.rpc import status_pb2
1112

1213

1314
def fake_response(
@@ -415,3 +416,93 @@ def test_debug_headers_enabled_shows_headers():
415416
assert "debug-token-12345" in error_message
416417
assert "X-Databricks-Azure-SP-Management-Token" in error_message
417418
assert "debug-azure-token-67890" in error_message
419+
420+
def test_protobuf_error_deserializer_valid_protobuf():
421+
# Create a valid protobuf Status message
422+
status = status_pb2.Status()
423+
status.code = 3 # INVALID_ARGUMENT
424+
status.message = "Invalid parameter provided"
425+
serialized_status = status.SerializeToString()
426+
427+
resp = fake_raw_response(
428+
method="POST",
429+
status_code=400,
430+
response_body=serialized_status,
431+
)
432+
433+
parser = errors._Parser()
434+
error = parser.get_api_error(resp)
435+
436+
assert isinstance(error, errors.BadRequest)
437+
assert str(error) == "Invalid parameter provided"
438+
439+
440+
def test_protobuf_error_deserializer_invalid_protobuf():
441+
# Create a response with invalid protobuf data that should fall through to other parsers
442+
resp = fake_raw_response(
443+
method="POST",
444+
status_code=400,
445+
response_body=b"\x00\x01\x02\x03\x04\x05", # Invalid protobuf
446+
)
447+
448+
parser = errors._Parser()
449+
error = parser.get_api_error(resp)
450+
451+
# Should fall back to the generic error handler
452+
assert isinstance(error, errors.BadRequest)
453+
assert "unable to parse response" in str(error)
454+
455+
456+
def test_protobuf_error_deserializer_empty_message():
457+
# Create a protobuf Status message with empty message
458+
status = status_pb2.Status()
459+
status.code = 5 # NOT_FOUND
460+
status.message = ""
461+
serialized_status = status.SerializeToString()
462+
463+
resp = fake_raw_response(
464+
method="GET",
465+
status_code=404,
466+
response_body=serialized_status,
467+
)
468+
469+
parser = errors._Parser()
470+
error = parser.get_api_error(resp)
471+
472+
assert isinstance(error, errors.NotFound)
473+
assert str(error) == "None"
474+
475+
476+
def test_protobuf_error_deserializer_with_details():
477+
# Create a protobuf Status message with details
478+
status = status_pb2.Status()
479+
status.code = 9 # FAILED_PRECONDITION
480+
status.message = "Resource is in an invalid state"
481+
serialized_status = status.SerializeToString()
482+
483+
resp = fake_raw_response(
484+
method="POST",
485+
status_code=400,
486+
response_body=serialized_status,
487+
)
488+
489+
parser = errors._Parser()
490+
error = parser.get_api_error(resp)
491+
492+
assert isinstance(error, errors.BadRequest)
493+
assert str(error) == "Resource is in an invalid state"
494+
495+
496+
def test_protobuf_error_deserializer_priority():
497+
resp = fake_valid_response(
498+
method="POST",
499+
status_code=400,
500+
error_code="INVALID_REQUEST",
501+
message="Invalid request body",
502+
)
503+
504+
parser = errors._Parser()
505+
error = parser.get_api_error(resp)
506+
507+
assert isinstance(error, errors.BadRequest)
508+
assert str(error) == "Invalid request body"

0 commit comments

Comments
 (0)