Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cf41e28
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
c4c1132
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
b61d459
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
7389448
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
5140063
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
39892cd
fix: apply solution for issue #759
genesisrevelationinc-debug May 25, 2026
d6fd74e
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
aaedea0
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
5a709bd
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
84d856f
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
a6c622e
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
aefc97b
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
520006e
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
96cd158
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
54cfc26
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
c1981c2
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
6fd489a
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
5930749
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
dfb2f76
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
08a9b9b
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
0cc54ed
fix: apply solution for issue #759
genesisrevelationinc-debug Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions _provenance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"tool_name": "ShanaBoo",
"boot_context": "You are ShanaBoo, an elite autonomous software engineer.\nYou are solving a real paid GitHub issue. Your goal is to:\n1. Understand the issue description thoroughly\n2. Identify the root cause / required change\n3. Write COMPLETE, production-quality code that fixes the issue\n4. Output ONLY the file changes as
56 changes: 39 additions & 17 deletions fastapi/fastapi/encoders.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import dataclasses
import datetime
import base64
from collections import defaultdict, deque
from collections.abc import Callable
from decimal import Decimal
Expand Down Expand Up @@ -85,13 +86,12 @@ def decimal_encoder(dec_value: Decimal) -> int | float:
bytes: lambda o: o.decode(),
Color: str,
PyExtraColor: str,


ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
Color: str,
PyExtraColor: str,
datetime.date: isoformat,
datetime.datetime: isoformat,
datetime.time: isoformat,
datetime.timedelta: lambda td: td.total_seconds(),
Decimal: decimal_encoder,
Enum: lambda o: o.value,
frozenset: list,
deque: list,
GeneratorType: list,
IPv4Address: str,
Expand Down Expand Up @@ -157,11 +157,23 @@ def jsonable_encoder(
bool,
Doc(
"""
Pydantic's `by_alias` parameter, passed to Pydantic models to define if
the output should use the alias names (when provided) or the Python
attribute names. In an API, if you set an alias, it's probably because you
want to use it in the result, so you probably want to leave this set to
`True`.
fields to exclude.

"""
),
] = None,
bytes_encoding: Annotated[
str,
Doc(
"""
The encoding to use for bytes and memoryview objects.
Defaults to "base64", can be set to "hex".
"""
),
] = "base64",
exclude_unset: Annotated[
bool,
Doc(
"""
),
] = True,
Expand Down Expand Up @@ -229,12 +241,22 @@ def jsonable_encoder(
[FastAPI docs for JSON Compatible Encoder](https://fastapi.tiangolo.com/tutorial/encoder/).
"""
custom_encoder = custom_encoder or {}
if custom_encoder:
if type(obj) in custom_encoder:
return custom_encoder[type(obj)](obj)
else:
for encoder_type, encoder_instance in custom_encoder.items():
if isinstance(obj, encoder_type):
)
return encoded_list

if isinstance(obj, bytes):
if bytes_encoding == "hex":
return obj.hex()
return base64.b64encode(obj).decode("ascii")

if isinstance(obj, memoryview):
if bytes_encoding == "hex":
return obj.tobytes().hex()
return base64.b64encode(obj).decode("ascii")

if dataclasses.is_dataclass(obj):
obj_dict = dataclasses.asdict(obj)
return jsonable_encoder(
return encoder_instance(obj)
if include is not None and not isinstance(include, (set, dict)):
include = set(include) # type: ignore[assignment] # ty: ignore[invalid-assignment]
Expand Down
181 changes: 181 additions & 0 deletions shanaboo_solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
```diff
--- a/fastapi/fastapi/encoders.py
+++ b/fastapi/fastapi/encoders.py
@@ -1,5 +1,6 @@
import dataclasses
import datetime
+import base64
from collections import defaultdict, deque
from collections.abc import Callable
from decimal import Decimal
@@ -56,7 +57,6 @@


ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
- bytes: lambda o: o.decode(),
Color: str,
PyExtraColor: str,
datetime.date: isoformat,
@@ -96,6 +96,7 @@
def jsonable_encoder(
obj: Annotated[
Any,
+ Doc(
"""
The input object to convert to JSON.
"""
@@ -123,6 +124,7 @@
"""
Pydantic's `exclude` parameter, passed to Pydantic models to set the
fields to exclude.
+
"""
),
] = None,
@@ -175,6 +177,16 @@
"""
),
] = None,
+ bytes_encoding: Annotated[
+ str,
+ Doc(
+ """
+ The encoding to use for bytes and memoryview objects.
+ Can be "base64" (default) or "hex".
+ """
+ ),
+ ] = "base64",
) -> Any:
+ if bytes_encoding not in ("base64", "hex"):
+ raise ValueError("bytes_encoding must be 'base64' or 'hex'")
+
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)

@@ -194,6 +206,16 @@
return obj.value
if isinstance(obj, Enum):
return obj.value
+ if isinstance(obj, bytes):
+ if bytes_encoding == "base64":
+ return base64.b64encode(obj).decode("ascii")
+ else:
+ return obj.hex()
+ if isinstance(obj, memoryview):
+ bytes_data = obj.tobytes()
+ if bytes_encoding == "base64":
+ return base64.b64encode(bytes_data).decode("ascii")
+ else:
+ return bytes_data.hex()
if isinstance(obj, PurePath):
return str(obj)
if isinstance(obj, (str, int, float)):
@@ -222,7 +244,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
# Check if type has a custom origin (like Annotated, list, set, etc.)
try:
@@ -240,7 +262,7 @@
for class_tuple in classes_tuple:
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(class_tuple, type) and issubclass(class_tuple, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)

try:
# pydantic v1 style
@@ -256,7 +278,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -273,7 +295,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -283,7 +305,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -296,7 +318,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -309,7 +331,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -319,7 +341,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -329,7 +351,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -339,7 +361,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -349,7 +371,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -359,7 +381,7 @@
)
for encoder_type, encoder in ENCODERS_BY_TYPE.items():
if isinstance(obj, encoder_type):
- return encoder(obj)
+ return encoder(obj, bytes_encoding=bytes_encoding) if encoder_type in (bytes, memoryview) else encoder(obj)
else:
raise # pragma: no cover

@@ -369,7 +391,7 @@