committed by
GitHub
4 changed files with 191 additions and 0 deletions
@ -0,0 +1,60 @@ |
|||
import time |
|||
|
|||
from fastapi.encoders import jsonable_encoder |
|||
from pydantic import BaseModel |
|||
|
|||
|
|||
class SubModel(BaseModel): |
|||
name: str |
|||
value: int = 42 |
|||
|
|||
|
|||
class MainModel(BaseModel): |
|||
id: int |
|||
title: str |
|||
sub: SubModel |
|||
items: list[SubModel] |
|||
maybe: str | None = None |
|||
|
|||
|
|||
def run_benchmark(): |
|||
sub = SubModel(name="test") |
|||
model = MainModel( |
|||
id=1, |
|||
title="hello", |
|||
sub=sub, |
|||
items=[sub] * 50, # 50 items to have a decent dictionary size |
|||
) |
|||
|
|||
iterations = 20000 |
|||
print(f"Benchmarking jsonable_encoder over {iterations} iterations...") |
|||
|
|||
# 1. Optimized Path (Direct return) |
|||
# Warmup |
|||
for _ in range(100): |
|||
jsonable_encoder(model) |
|||
|
|||
start_time = time.perf_counter() |
|||
for _ in range(iterations): |
|||
jsonable_encoder(model) |
|||
optimized_time = time.perf_counter() - start_time |
|||
|
|||
# 2. Original Path (Double serialization via model_dump + recursive dict encoding) |
|||
# Warmup |
|||
for _ in range(100): |
|||
jsonable_encoder(model.model_dump(mode="json")) |
|||
|
|||
start_time = time.perf_counter() |
|||
for _ in range(iterations): |
|||
# We simulate the exact old logic: model_dump(mode="json") followed by recursive jsonable_encoder |
|||
obj_dict = model.model_dump(mode="json") |
|||
jsonable_encoder(obj_dict) |
|||
original_time = time.perf_counter() - start_time |
|||
|
|||
print(f"Original Code Path: {original_time:.4f} seconds") |
|||
print(f"Optimized Code Path: {optimized_time:.4f} seconds") |
|||
print(f"Speedup: {original_time / optimized_time:.2f}x") |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
run_benchmark() |
|||
@ -0,0 +1,51 @@ |
|||
from fastapi.encoders import jsonable_encoder |
|||
from pydantic import BaseModel |
|||
|
|||
|
|||
class SubModel(BaseModel): |
|||
name: str |
|||
value: int = 42 |
|||
|
|||
|
|||
class MainModel(BaseModel): |
|||
id: int |
|||
title: str |
|||
sub: SubModel |
|||
items: list[SubModel] |
|||
maybe: str | None = None |
|||
_sa_instance_state: str = "should-be-removed" |
|||
|
|||
|
|||
def test_basemodel_serialization_correctness(): |
|||
sub = SubModel(name="test") |
|||
model = MainModel( |
|||
id=1, |
|||
title="hello", |
|||
sub=sub, |
|||
items=[sub, SubModel(name="another", value=10)], |
|||
) |
|||
|
|||
# 1. Standard serialization |
|||
encoded = jsonable_encoder(model) |
|||
assert encoded == { |
|||
"id": 1, |
|||
"title": "hello", |
|||
"sub": {"name": "test", "value": 42}, |
|||
"items": [ |
|||
{"name": "test", "value": 42}, |
|||
{"name": "another", "value": 10}, |
|||
], |
|||
"maybe": None, |
|||
} |
|||
|
|||
# 2. Exclude none |
|||
encoded_exclude_none = jsonable_encoder(model, exclude_none=True) |
|||
assert "maybe" not in encoded_exclude_none |
|||
|
|||
# 3. Include and Exclude parameter filtering |
|||
encoded_filtered = jsonable_encoder(model, include={"id", "title"}) |
|||
assert encoded_filtered == {"id": 1, "title": "hello"} |
|||
|
|||
encoded_excluded = jsonable_encoder(model, exclude={"sub", "items"}) |
|||
assert "sub" not in encoded_excluded |
|||
assert "items" not in encoded_excluded |
|||
Loading…
Reference in new issue