feat: Handle HTTP responses with HTML in body, add retry handling for python sdk on transient errors, update docs for .NET about retry handling#126
Conversation
|
Nice work, the CancellationToken threading and the HTML-body test coverage are great. One architectural concern before merging. The retry layer collides with AddStandardResilienceHandler. Our DI registration already calls Proposal:
|
Ok, and for the python package: i dont think we have any extension packages there that we can use? |
|
Discussed outside the thread. The SCADA connector already applies resiliency by calling AddHeimdallPowerApiClient - so it will retry after this merge when handling the HTML response body and the transient HTTP Status Codes instead of throwing an exception. The python sdk will also do some simple retry handling. Added some tests, updated docstrings and examples. |
…with extensions package, update READMEs
Co-authored-by: Sarah Sayeed Qureshi <sarahsayeedq@gmail.com>
Add retry logic, graceful error handling, and cancellation token support
Summary
Fixes a crash where the SDK threw an unhandled
JsonExceptionwhen Application Gateway returned a504 Gateway Timeout(or500) with an HTML response body instead of JSON.Adds automatic retry with exponential backoff for transient gateway errors (via
AddStandardResilienceHandlerin the Extensions package), fullCancellationTokenpropagation throughout the .NET SDK, andtimeoutsupport in Python.Bug fixed
Unexpected character encountered while parsing value: <. Path '', line 0, position 0When Application Gateway returns
500or504with an HTML error page, the SDK previously attempted to deserialize it asProblemDetailsJSON — causing an unhandledJsonExceptionto propagate to the caller. The SDK now catchesJsonExceptiongracefully and wraps it in a cleanHeimdallApiException/HeimdallApiError.Changes
.NET
HeimdallApiHttpClient.csJsonExceptionon HTML error bodies; includesCancellationTokeninHandleResponse,ExecuteWithAuthRetry, andRefreshAccessToken; passescancellationTokentoAcquireTokenAsyncAccessTokenProvider.csIAccessTokenProvider.AcquireTokenAsyncand implementation now acceptCancellationToken; token is forwarded to MSAL'sExecuteAsyncIHeimdallApiClient.csCancellationToken cancellationToken = default; includes newly mergedGetSagAndClearancesAsyncandGetIcingsAsyncHeimdallApiClient.csCancellationToken; includes newly mergedGetSagAndClearancesAsyncandGetIcingsAsyncHeimdallApiClientExtensions.csAddStandardResilienceHandleron the namedHttpClient— gives retry, circuit breaker, and total-timeout pipeline automatically when using DIWhenHandlingErrorResponses/WhenUsingResilienceExtensions/AddHeimdallPowerApiClientGetCircuitRatings,GetConductorTemperatures,GetCurrents,GetHeimdallAars,GetHeimdallDlrsREADME.mdProgram.cs(example)Python
errors.pyHeimdallApiError(Exception)withstatus_codeandis_transient()client.py_execute_with_retrywith exponential backoff; newtimeoutconstructor parametercapacity_monitoring.py,grid_insights.py,assets.pyHeimdallApiErrorinstead of bareException__init__.pyHeimdallApiErrortests/unit/test_retry_behavior.pyREADME.mdprint_heimdall_dlr.py(example)HeimdallApiError, commented timeout exampleRetry behaviour
.NET — Extensions package only
The core package (
HeimdallPower.Api.Client) does not retry automatically. Transient errors are thrown immediately asHeimdallApiException.The Extensions package (
HeimdallPower.Api.Client.Extensions) adds a full resilience pipeline viaAddStandardResilienceHandlerwhen registering withAddHeimdallPowerApiClient:HttpRequestException502 Bad Gateway503 Service Unavailable504 Gateway Timeout500 Internal Server Error400,401,403,404HttpRequestException/ network errorPython — built into core client
JsonExceptionHeimdallApiErroris re-raised with the original status codeBreaking changes
.NET — BREAKING CHANGE (minor)
Two sets of changes affect implementors:
IHeimdallApiClient— all method signatures now includeCancellationToken cancellationToken = defaultas the last parameter, including the newly addedGetSagAndClearancesAsyncandGetIcingsAsync.IAccessTokenProvider.AcquireTokenAsync— now acceptsCancellationToken(internal interface, only relevant if you have custom token provider implementations).IHeimdallApiClient— compile error; addCancellationToken cancellationToken = defaultto each methodRecommended: bump minor version (semver).
Python — No breaking changes
HeimdallApiErroris a new class (additive)timeoutconstructor parameter defaults toNone(existing behaviour unchanged)Exceptioncontinue to work; callers can now catchHeimdallApiErrorspecifically for richer error info