GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — perf: build the StatusError message on first use by lesnik512 · Pull Request #180 · modern-python/httpware

Conversation

@lesnik512

@lesnik512 lesnik512 commented Oct 17, 2026

Copy link
Copy Markdown
Member

StatusError.__init__ used to format "<status> <method> <redacted url>" as soon as the error was built. That meant str(request.url) plus redact_url on every 4xx/5xx, and Retry paid it again on every retried attempt, even though nothing reads the message unless the error is printed or logged.

Now __init__ only stores response. BaseException.__new__ has already put (response,) in the C-level args slot. StatusError.args is a property: the first time it is read and still holds that (response,), it builds the summary, writes (summary,) back into the slot, and returns it. Later reads return the cached tuple. __str__ reads self.args, so str(), tracebacks and logging all go through the same path. Assigning exc.args still works, and the assigned value replaces the message the way it does on any exception.

Nothing a user can see changes:

  • exc.args is still ("<status> <method> <redacted url>",). A cheaper-looking option was to skip super().__init__ and leave args as (response,). Its raise cost is the same, because the property only runs when someone reads args, but it changes args in a way users can see, so I did not take it.
  • str(exc), repr(exc), traceback.format_exception, the uncaught-exception hook and logging exc_info output are byte-identical to main for StatusError, ClientStatusError, ServerStatusError and every class in STATUS_TO_EXCEPTION. I checked this with URLs that have userinfo, a port, an IPv6 host, query values and fragment parameters, with and without add_note() and a user attribute.
  • Pickle, copy.copy and copy.deepcopy round-trip unchanged. ClientError.__reduce__ reads self.args, which builds the message, so the restored instance carries the same redacted string, and __dict__ holds no new keys.
  • The URL is still redacted, because the message is built by the same _summary() as before.

Measured on 3.14, best of 9, three alternating runs (the machine is noisy):

case main this PR
raise + catch, plain URL 1.3 to 2.7 us 0.5 to 1.1 us
raise + catch, URL with userinfo and query 4.1 to 8.2 us 0.6 to 1.1 us
construct + str(), plain URL 1.1 to 2.3 us 1.3 to 2.9 us
construct + str(), userinfo and query 3.5 to 8.0 us 3.8 to 8.8 us

When the message is read, the property costs about 0.2 to 0.3 us extra. That path only runs when an error is printed or logged.

Dropped: resolver memoization

I also measured caching the resolved decoder per model. It saved about 0.2 to 1.3 us per resolve() and under 1% end to end, and I dropped it because it would call a custom decoder's can_decode once per model instead of once per request.

Behaviour change for the 0.22.0 notes

None.

Tests

test_errors.py: args holds the redacted summary, construction does not call redact_url, and assigned args of length 0, 1 and 2 replace the message.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@lesnik512
perf: build the StatusError message on first use instead of at constr…
c986987
…uction
@lesnik512
lesnik512 merged commit 9336c45 into main Oct 17, 2026
13 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@lesnik512
lesnik512 deleted the perf/errors-decoders branch October 17, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@lesnik512
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.