Skip to content

Exclude with nested "__all__" does not work as expected #1579

@xspirus

Description

@xspirus

Bug

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.5.1
            pydantic compiled: False
                 install path: /home/spirus/projects/pydantic/pydantic
               python version: 3.8.3 (default, May 17 2020, 18:15:42)  [GCC 10.1.0]
                     platform: Linux-5.6.14-arch1-1-x86_64-with-glibc2.2.5
     optional deps. installed: ['typing-extensions', 'email-validator', 'devtools']

As I was experimenting with FastAPI and pydantic, I thought that it would be nice to use exclude in order to avoid some Schema duplication (or complex inheritances). Ultimately, I came across a weird bug when trying to use exclude on nested sequences. Below is a minimal example for reproducing the bug.

from typing import List

from pydantic import BaseModel


class A(BaseModel):
    a: int = 1
    b: int = 2
    c: int = 3


class B(BaseModel):
    a: List[A]


class C(BaseModel):
    b: List[B]


def test():
    c = C(b=[B(a=[A()])])
    print(c.dict(exclude={"b": {0: {"a": {"__all__": {"c"}}}}}))
    print(c.dict(exclude={"b": {"__all__": {"a": {"__all__": {"c"}}}}}))
    assert c.dict(exclude={"b": {0: {"a": {"__all__": {"c"}}}}}) == {"b": [{"a": [{"a": 1, "b": 2}]}]}
    assert c.dict(exclude={"b": {"__all__": {"a": {"__all__": {"c"}}}}}) == {"b": [{"a": [{"a": 1, "b": 2}]}]}

Running the above function with pytest the output is:

Test session starts (platform: linux, Python 3.8.3, pytest 5.3.5, pytest-sugar 0.9.2)
rootdir: /home/spirus/projects/pydantic, inifile: setup.cfg
plugins: cov-2.8.1, mock-3.1.0, sugar-0.9.2
collecting ... 

―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― test ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

    def test():
        c = C(b=[B(a=[A()])])
        print(c.dict(exclude={"b": {0: {"a": {"__all__": {"c"}}}}}))
        print(c.dict(exclude={"b": {"__all__": {"a": {"__all__": {"c"}}}}}))
        assert c.dict(exclude={"b": {0: {"a": {"__all__": {"c"}}}}}) == {"b": [{"a": [{"a": 1, "b": 2}]}]}
>       assert c.dict(exclude={"b": {"__all__": {"a": {"__all__": {"c"}}}}}) == {"b": [{"a": [{"a": 1, "b": 2}]}]}
E       AssertionError: assert {'b': [{}]} == {'b': [{'a': ...1, 'b': 2}]}]}
E         Differing items:
E         {'b': [{}]} != {'b': [{'a': [{'a': 1, 'b': 2}]}]}
E         Use -v to get the full diff

example.py:25: AssertionError
------------------------------------------------------------ Captured stdout call ------------------------------------------------------------
{'b': [{'a': [{'a': 1, 'b': 2}]}]}
{'b': [{}]}

 example.py ⨯                                                                                                                  100% ██████████

Results (0.05s):
       1 failed
         - example.py:20 test

I inspected the codebase a little bit, and found out that the bug is produced because of how _normalize_indexes, as it assumes that when the items is a dict containing the __all__ key, then the value is considered by default to be a set. So calling the update, information may be missed.

I am not certain if my usage violates how the __all__ key should be used, but if this should have been correct, I can create a PR with a solution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug V1Bug related to Pydantic V1.X

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions