Skip to content

fix: lead APIError's string form with the error message - #724

Open
Max Azatian (HardMax71) wants to merge 3 commits into
microsoft:mainfrom
HardMax71:fix/api-error-str-leads-with-message
Open

fix: lead APIError's string form with the error message#724
Max Azatian (HardMax71) wants to merge 3 commits into
microsoft:mainfrom
HardMax71:fix/api-error-str-leads-with-message

Conversation

@HardMax71

@HardMax71 Max Azatian (HardMax71) commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Overview

APIError.__str__ returned a multi-line string that started with a newline and printed message: None for every generated error model, because the generator never sets message; it emits a primary_message property that __str__ ignored. Anything that titles an exception by its first line (Sentry does exactly value.splitlines()[0], most log viewers do the same) showed ODataError: with nothing after it, and logging.error("...: %s", err) printed a blank line followed by an indented block.

The string now leads with the message: primary_message when the subclass defines it, else message, else the class name; the status code follows in parentheses on the same line, and the nested error payload stays on a second line so nothing that was printed before is lost. For the throttling example from the issue:

Application is over its MailboxConcurrency limit. (status 429)
error: MainError(additional_data={}, code='ApplicationThrottled', ..., message='Application is over its MailboxConcurrency limit.', target=None)

This matches the dotnet abstraction, where the generated error overrides Message with Error?.Message.

Related Issue

Fixes #723

Notes

The base class knows neither primary_message nor error, so both are read with getattr and are optional; a plain APIError(message="boom") prints boom, and one with nothing set prints APIError (status 502). The getattr(self, "error", None) guard from #133 is kept.

Testing Instructions

  • cd packages/abstractions && pytest tests/test_api_error.py: five cases covering a bare message, the status suffix, the class-name fallback, a generated-style subclass with error and primary_message, and that no variant starts with a blank line. All five fail on main.
  • The whole abstractions suite: 139 passed. yapf, isort and mypy clean; pylint reports only the pre-existing .pylintrc option warnings.

str(APIError) started with a newline and printed message: None for every generated error model, ignoring the primary_message the generator emits, so any log line or error title built from the first line was empty. The first line now carries the message (primary_message, then message, then the class name), the status code and the error code; the nested error payload follows on a second line.
@HardMax71
Max Azatian (HardMax71) requested a review from a team as a code owner September 7, 2026 14:22
Copilot AI lite review requested due to automatic review settings September 7, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The formatting change is localized, matches the stated issue/expected behavior, and is covered by targeted new tests for the key scenarios.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@baywet Vincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!

Comment thread packages/abstractions/kiota_abstractions/api_error.py Outdated
The base class has no notion of an error code; `error.code` is the OData
error contract as Graph exposes it. The first line is now the message and
the status only, the code stays visible on the error line.
@sonarqubecloud

Copy link
Copy Markdown

Comment on lines 17 to +18
error = getattr(self, "error", None)
message = getattr(self, "primary_message", None) or self.message or type(self).__name__

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also confused about those two fields: they are not present in the base class? or defined here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on the base class, both come from the generated error models. error is the field the generator makes for the API's error schema, on Graph that is ODataError.error. The base class already read it with getattr before this PR (added in #137). primary_message is a property the generator adds to every error model whose schema marks a field with x-ms-primary-error-message (PythonRefiner.cs#L153, CommonLanguageRefiner.cs#L1577), on Graph here.

So the base class only uses them when the subclass has them. If you'd like to, I can add primary_message to APIError as a property that returns self.message, the generated classes already override it and __str__ can then read it directly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the additional information.
I think the python implementation has strayed from the other languages here. The behaviour is typically for the rendering method to be overloaded and generated, see this example which is based on this class which exposes additional properties (like the status code and response headers), but doesn't rely on reflection to format the message.

Unless Python has some peculiar language constraints I can't remember, we should probably align things here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

APIError.__str__ starts with a newline and ignores the generated primary_message, so every error is titled "ODataError:" with nothing after it

3 participants