Python 3.14: `APIMethods._return` raises DeserializeError for plain text responses (Union singleton identity check)
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 84/100
Research direction
Start in python/looker_sdk/rtl/api_methods.py at APIMethods._return and reproduce the issue with the minimal run_inline_query example on Python 3.14. Add a regression test for a Union[str, bytes] plain-text response, then verify that it returns the raw value without DeserializeError on supported Python versions.
Written by the indexing model from the issue text.
Description
Summary
On Python 3.14, every Looker SDK call whose declared return type is Union[str, bytes] (e.g. run_inline_query(result_format="sql"|"csv"|...), query_for_slug(...) paths that return raw strings, run_query, run_look, run_url_encoded_query, etc.) raises:
looker_sdk.rtl.serialize.DeserializeError: Bad json Expecting value: line 1 column 1 (char 0)
…even though the response body is a perfectly well-formed plain string (e.g. raw SQL, CSV, TSV).
Root cause
python/looker_sdk/rtl/api_methods.py:109 compares the requested deserialization structure against Union[str, bytes] using is:
if structure is Union[str, bytes] or structure is str or value == "": # type: ignore
ret = value
else:
ret = self.deserialize(data=value, structure=structure) # type: ignore
In CPython 3.14, typing.Union and types.UnionType were merged, and old-syntax Union[...] objects are no longer cached as singletons (see CPython gh-105499 and Python 3.14 What's New, which explicitly states "Use == to compare unions for equality, not is.").
As a result:
>>> from typing import Union
>>> Union[str, bytes] is Union[str, bytes] # Python 3.14
False
>>> Union[str, bytes] == Union[str, bytes]
True
The generated SDK methods pass Union[str, bytes] as structure, so the is branch in _return never matches on 3.14 — the SDK falls through to json.loads(value) on plain text and raises DeserializeError.
The # type: ignore comment on line 109 suggests the original author already knew the identity check on a generic alias was fragile; CPython 3.14 just made it actually break.
Affected versions
| Bug present? | |
|---|---|
looker_sdk==25.20.0 (and earlier 25.x) on Python 3.14 |
Yes |
looker_sdk==26.6.1 (latest release as of 2026-04-28) on Python 3.14 |
Yes |
main HEAD on Python 3.14 |
Yes |
| Any version on Python ≤ 3.13 | No (Union singletons still cached) |
The Python SDK declares python_requires=">=3.6" with no upper bound, so users on 3.14 install fine and only hit the bug at runtime.
Minimal repro
import looker_sdk
sdk = looker_sdk.init40()
# Any call that returns Union[str, bytes] reproduces:
sql = sdk.run_inline_query(
result_format="sql",
body=looker_sdk.models40.WriteQuery(
model="thelook", view="users", fields=["users.id"], limit="10",
),
)
# Python <= 3.13: returns the SQL string.
# Python 3.14: raises DeserializeError("Bad json Expecting value: line 1 column 1 (char 0)").
Or without any HTTP call at all:
from typing import Union
assert Union[str, bytes] is Union[str, bytes] # AssertionError on 3.14
Suggested fix
One-line change at python/looker_sdk/rtl/api_methods.py:109 — pre-cache the union and compare with ==:
# At module scope:
_UNION_STR_BYTES = Union[str, bytes]
# In APIMethods._return:
if structure == _UNION_STR_BYTES or structure is str or value == "":
ret = value
else:
ret = self.deserialize(data=value, structure=structure)
This is backwards compatible to all currently-supported Python versions (3.6+).
Workaround
For projects that need to ship on 3.14 today, monkey-patching APIMethods._return at import time is straightforward — substitute str for Union[str, bytes] before delegating to the original implementation, so the SDK's existing structure is str branch fires:
from looker_sdk.rtl import api_methods
_UNION_STR_BYTES = str | bytes # equals Union[str, bytes] at runtime
_original_return = api_methods.APIMethods._return
def _return_patched(self, response, structure):
if structure == _UNION_STR_BYTES:
structure = str
return _original_return(self, response, structure)
api_methods.APIMethods._return = _return_patched
Happy to send a PR with the one-line fix + a regression test if that helps.
References
- CPython issue: python/cpython#105499 — Merge
typing.Unionandtypes.UnionType - Python 3.14 release notes: "Use
==to compare unions for equality, notis" - Related historical SDK issue (predates 3.14, same code path): #62 — Python: support
Union[SomeNonStrType, bytes]deserialization
- Dominant language
- TypeScript
- Stars
- 272
- Forks
- 203
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from looker-open-source/sdk-codegen
-
need triage p3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
looker-open-source/sdk-codegen#1741 ·
-
need triage p3
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
looker-open-source/sdk-codegen#1714 ·
-
bug p3
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
looker-open-source/sdk-codegen#1677 ·
-
p3
Difficulty 1/5 Under an hour Newbie friendliness 68/100
looker-open-source/sdk-codegen#487 · 2 comments ·
-
need triage p3
Difficulty 3/5 1-2 days Newbie friendliness 55/100
looker-open-source/sdk-codegen#1737 ·
All issues in looker-open-source/sdk-codegen
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100