Skip to content

fix(http): return last response for non-JSON bodies - #1653

Merged
LioriE merged 5 commits into
mainfrom
fix/get-last-response-non-json-body
Aug 10, 2026
Merged

fix(http): return last response for non-JSON bodies#1653
LioriE merged 5 commits into
mainfrom
fix/get-last-response-non-json-body

Conversation

@LioriE

@LioriE LioriE commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Related Issues

Fixes: https://github.com/descope/etc/issues/17556

In a Nutshell

  • get_last_response() no longer raises on non-JSON bodies
  • bool(response) now means "a response exists", and never parses the body
  • str/repr/== degrade gracefully instead of raising
  • str/repr bound the echoed non-JSON body at 200 chars
  • Explicit JSON access still raises, unchanged
  • New is_json property

Description

client.get_last_response() raised JSONDecodeError instead of returning the response when the body was not JSON, for example an nginx 502 HTML page. mgmt_resp or auth_resp evaluates truthiness, which called DescopeResponse.__bool__ -> json(). __str__ and __repr__ had the same problem, so even logging the returned object crashed, which is exactly the production logging path the customer hit.

__bool__ now returns True unconditionally. Truthiness answers "did I get a response", not "is the body non-empty", so it no longer touches the body at all. It stays explicitly defined rather than deleted, because __len__ exists and Python would fall back to it for truthiness, reintroducing the parse. __str__, __repr__ and __eq__ catch ValueError (json.JSONDecodeError subclasses it) and fall back to a bounded text preview, a status_code=... text=... summary, and identity. Explicit JSON access (json(), __getitem__, get, keys, items, __len__, __iter__) still raises, per the issue's expected behavior.

The non-JSON preview is capped at 200 chars in both __str__ and __repr__, since the body of an upstream error page is not size-controlled by us and the motivating path is logging it once per failed request. .text still returns the full body.

No caller-side change was needed: with __bool__ fixed, get_last_response() returns the response through the existing mgmt_resp or auth_resp, and if response: in the docs is a correct presence check again (a missing response is None, which stays falsy).

Behavior change worth a look in review: a response with an empty JSON body ({}) used to be falsy and is now truthy. That was the old dict-emulation semantics. Code doing if resp: to mean "the body had content" would need if resp.json():. Reachable only via get_last_response() under opt-in verbose=True, which shipped in 1.10.0.

Must

  • Tests
  • Documentation (if applicable)

get_last_response() raised JSONDecodeError instead of returning the
response when the body was not JSON (e.g. an nginx 502 HTML page):
`mgmt_resp or auth_resp` evaluates truthiness, which called
DescopeResponse.__bool__ -> json(). __str__ and __repr__ had the same
problem, so logging the response crashed too.

The inspection dunders now fall back on a parse failure instead of
raising, leaving JSON-body semantics unchanged. Explicit JSON access
(json(), __getitem__, get, keys, items, __len__, __iter__) still
raises. Adds an is_json property, and records the last response on
put(), which was missing in both the sync and async clients.
@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Review complete — View session on Shuni Portal 🐾

@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Suggested Reviewers

The selection focuses on contributors with multiple commits across core client and test files, ensuring a comprehensive review of the code changes and their impacts.

Reviewer Reason
omercnet omeret has contributions across multiple relevant files, including descope_client.py and http_client.py, indicating a broad understanding of the client codebase.
guyp-descope guyp-descope has made commits involving descope_client.py, http_client.py, and tests, making them suitable for reviewing changes in core client logic and testing coverage.
itaihanski itaihanski's commits span descope_client.py, http_client.py, and tests, providing a good perspective on the impact of code changes across different areas.
gaokevin1 gaokevin1's contributions to descope_client.py and test files suggest they can review the specific areas related to response handling and testing.
chris4490 chris4490's single commit in README.md might not be as crucial for technical review, so it is not selected.

Suggested by Shuni based on git history and PR context. Names are not @-mentioned to avoid notifying anyone — request a review from whoever fits best.

Comment thread tests/test_http_client.py Fixed
Comment thread tests/test_http_client.py Fixed

@shuni-bot shuni-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🐕 Shuni's Review

Makes DescopeResponse inspection dunders survive non-JSON bodies (nginx 502 pages), adds is_json, and records last_response on put() in both clients. The core fix is sound — ValueError correctly covers json.JSONDecodeError, JSON-body behavior is byte-for-byte unchanged, and explicit JSON access still raises as the issue requires. Good bones! 🦴

Sniffed out 3 issues:

  • 1 🟡 MEDIUM: is not None removes the accidental fall-through in get_last_response(), so a stale mgmt response now wins more often
  • 2 🟢 LOW: unbounded __str__ on non-JSON bodies, put() verbose capture untested

See inline comments for details. Woof!


Declared coverage: FULL — 9/9 changed files reviewed.

Comment thread descope/descope_client.py Outdated
Comment thread descope/_http_client_base.py Outdated
Comment thread descope/http_client.py Outdated
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Coverage report

The coverage rate went from 98.21% to 98.22% ⬆️

100% of new lines are covered.

Diff Coverage details (click to unfold)

descope/_http_client_base.py

100% of new lines are covered (100% of the complete file).

bool(response) answered "is the JSON body non-empty", which meant
truthiness parsed the body. It is now unconditionally True: an empty
JSON body no longer reads as no-response, and truthiness never parses.

Kept explicit rather than deleted, since __len__ is defined and Python
would otherwise fall back to it for truthiness, reintroducing both the
parse and the empty-body falsiness.
@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Uh oh! I ran into an error.

View failed run

Please check the logs for details.

@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Shuni hit a problem and couldn't finish.

Automated review ended without submitting a GitHub review for PR #1653

View workflow logs

Revert the get_last_response() accessor and the docs to plain truthiness
now that __bool__ no longer parses the body: `mgmt_resp or auth_resp` is
behaviorally identical to the explicit None check.

Address review:
- str() echoed the whole non-JSON body while repr() capped at 200 chars.
  Body size is upstream-controlled, so both now share one bounded preview
  and point at .text for the full body.
- put() verbose capture had no test in either client, which is how it was
  missed in the first place. Added to both.
@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Uh oh! I ran into an error.

View failed run

Please check the logs for details.

@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Shuni hit a problem and couldn't finish.

Automated review ended without submitting a GitHub review for PR #1653

View workflow logs

@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Uh oh! I ran into an error.

View failed run

Please check the logs for details.

@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Shuni hit a problem and couldn't finish.

Automated review ended without submitting a GitHub review for PR #1653

View workflow logs

The put() last-response gap belongs to descope/etc#16377, together with
the get_last_response() staleness half of that issue. Keeping it here
would half-close that issue and put changes outside this bug's scope
into a fix for descope/etc#17556.
@shuni-bot

shuni-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🐕 Review complete — View session on Shuni Portal 🐾

@shuni-bot shuni-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🐕 Shuni's Review

Re-review after 91f19586. The PR is now scoped to DescopeResponse alone: __bool__ is hard-coded True, str/repr/== degrade gracefully, and _text_preview() bounds the non-JSON echo at 200 chars. All three of my earlier findings are addressed — the truncation and the equality/truthiness semantics all check out, and the if response: / mgmt_resp or auth_resp call sites left on main are now safe rather than crashing. Good bones! 🦴

Sniffed out 0 code issues — no new inline findings survived verification.

One non-code note: 91f19586 reverted the put() verbose capture (impl and tests, both clients), but the description still says "put() now records the last response, in both clients" and "this PR fixes the put() verbose-capture half" of descope/etc#16377. put() on main still has no capture — worth updating the body so 16377 isn't closed early.

Heads up: my resolve_thread calls came back permission-denied, so the three prior threads stay open on GitHub despite being addressed. Replies are on each. Woof!


Declared coverage: FULL — 4/4 changed files reviewed.

@LioriE
LioriE requested a review from omercnet August 9, 2026 14:10
@LioriE

LioriE commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

@omercnet can u take a look? small fix

@LioriE
LioriE enabled auto-merge (squash) August 9, 2026 14:13
@LioriE
LioriE disabled auto-merge August 9, 2026 14:13
@LioriE
LioriE enabled auto-merge (squash) August 9, 2026 14:13
Comment thread descope/_http_client_base.py
@LioriE
LioriE merged commit 524aef3 into main Aug 10, 2026
38 checks passed
@LioriE
LioriE deleted the fix/get-last-response-non-json-body branch August 10, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants