When an error status has an error class registered but the response has no body (a 503 from a gateway, with no Content-Type), get_root_parse_node returns None (httpx_request_adapter.py#L420), _get_error_from_response returns None (#L493), and throw_failed_responses puts type(error) into the message (#L578). So the message ends in <class 'NoneType'>. kiota-dotnet builds the same message with the status code (HttpClientRequestAdapter.cs#L592), and the two "no error class is registered" messages in the same function already end in it (#L528, #L544).
In the same function, the get_object_value span is started before the None check (#L491), and .end() runs only after get_object_value returns (#L498). It is never ended when there is no body, and also not when get_object_value raises (the path #713 added, #L561).
Repro:
import asyncio
from importlib.metadata import version
import httpx
from kiota_abstractions.api_error import APIError
from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_abstractions.method import Method
from kiota_abstractions.request_information import RequestInformation
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
from opentelemetry import trace
from opentelemetry.sdk.trace import SpanProcessor, TracerProvider
started = []
class RecordStarts(SpanProcessor):
def on_start(self, span, parent_context=None):
started.append(span)
class ExampleError(APIError):
@staticmethod
def create_from_discriminator_value(parse_node):
return ExampleError()
def get_field_deserializers(self):
return {}
def serialize(self, writer):
pass
async def main():
provider = TracerProvider()
provider.add_span_processor(RecordStarts())
trace.set_tracer_provider(provider)
print(f"kiota-http {version('microsoft-kiota-http')}")
# a 503 from a gateway, with no body and no Content-Type
transport = httpx.MockTransport(lambda request: httpx.Response(503))
async with httpx.AsyncClient(transport=transport) as client:
adapter = HttpxRequestAdapter(AnonymousAuthenticationProvider(), http_client=client)
info = RequestInformation(Method.POST, "https://graph.microsoft.com/v1.0/me/sendMail")
try:
await adapter.send_no_response_content_async(info, {"5XX": ExampleError})
except APIError as e:
print(f"status {e.response_status_code}, message: {e.message!r}")
print(f"spans never ended: {[s.name for s in started if s.end_time is None]}")
asyncio.run(main())
kiota-http 1.14.0
status 503, message: "The server returned an unexpected status code and the error registered for this code failed to deserialize: <class 'NoneType'>"
spans never ended: ['get_object_value']
Not covered by #713 (a body that cannot be parsed) or #724 (APIError.__str__); #508 was about other spans.
Fix: the status code in the message, as in kiota-dotnet; start the get_object_value span only when there is a body to deserialize, and end it in a finally, as get_root_parse_node does with its own span (#L423). With that, the script prints failed to deserialize: 503 and spans never ended: []. PR follows.
When an error status has an error class registered but the response has no body (a 503 from a gateway, with no
Content-Type),get_root_parse_nodereturnsNone(httpx_request_adapter.py#L420),_get_error_from_responsereturnsNone(#L493), andthrow_failed_responsesputstype(error)into the message (#L578). So the message ends in<class 'NoneType'>. kiota-dotnet builds the same message with the status code (HttpClientRequestAdapter.cs#L592), and the two "no error class is registered" messages in the same function already end in it (#L528, #L544).In the same function, the
get_object_valuespan is started before theNonecheck (#L491), and.end()runs only afterget_object_valuereturns (#L498). It is never ended when there is no body, and also not whenget_object_valueraises (the path #713 added, #L561).Repro:
Not covered by #713 (a body that cannot be parsed) or #724 (
APIError.__str__); #508 was about other spans.Fix: the status code in the message, as in kiota-dotnet; start the
get_object_valuespan only when there is a body to deserialize, and end it in afinally, asget_root_parse_nodedoes with its own span (#L423). With that, the script printsfailed to deserialize: 503andspans never ended: []. PR follows.