Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix jsonable_encoder using include and exclude parameters for non-Pydantic objects #2606

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion fastapi/encoders.py
Expand Up @@ -80,6 +80,11 @@ def jsonable_encoder(
return obj
if isinstance(obj, dict):
encoded_dict = {}
allowed_keys = set(obj.keys())
if include is not None:
allowed_keys &= set(include)
if exclude is not None:
allowed_keys -= set(exclude)
for key, value in obj.items():
if (
(
Expand All @@ -88,7 +93,7 @@ def jsonable_encoder(
or (not key.startswith("_sa"))
)
and (value is not None or not exclude_none)
and ((include and key in include) or not exclude or key not in exclude)
and key in allowed_keys
):
encoded_key = jsonable_encoder(
key,
Expand Down Expand Up @@ -144,6 +149,8 @@ def jsonable_encoder(
raise ValueError(errors)
return jsonable_encoder(
data,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
Expand Down
34 changes: 34 additions & 0 deletions tests/test_jsonable_encoder.py
Expand Up @@ -93,16 +93,42 @@ class Config:
return ModelWithPath(path=request.param("/foo", "bar"))


def test_encode_dict():
pet = {"name": "Firulais", "owner": {"name": "Foo"}}
assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}}
assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, include={}) == {}
assert jsonable_encoder(pet, exclude={}) == {
"name": "Firulais",
"owner": {"name": "Foo"},
}


def test_encode_class():
person = Person(name="Foo")
pet = Pet(owner=person, name="Firulais")
assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}}
assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, include={}) == {}
assert jsonable_encoder(pet, exclude={}) == {
"name": "Firulais",
"owner": {"name": "Foo"},
}


def test_encode_dictable():
person = DictablePerson(name="Foo")
pet = DictablePet(owner=person, name="Firulais")
assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}}
assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"}
assert jsonable_encoder(pet, include={}) == {}
assert jsonable_encoder(pet, exclude={}) == {
"name": "Firulais",
"owner": {"name": "Foo"},
}


def test_encode_unsupported():
Expand Down Expand Up @@ -144,6 +170,14 @@ def test_encode_model_with_default():
assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == {
"foo": "foo"
}
assert jsonable_encoder(model, include={"foo"}) == {"foo": "foo"}
assert jsonable_encoder(model, exclude={"bla"}) == {"foo": "foo", "bar": "bar"}
assert jsonable_encoder(model, include={}) == {}
assert jsonable_encoder(model, exclude={}) == {
"foo": "foo",
"bar": "bar",
"bla": "bla",
}


def test_custom_encoders():
Expand Down