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

Broken loading list of tuples. #1190

Closed
DMantis opened this issue Jan 26, 2020 · 2 comments · Fixed by #1253
Closed

Broken loading list of tuples. #1190

DMantis opened this issue Jan 26, 2020 · 2 comments · Fixed by #1253
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@DMantis
Copy link

DMantis commented Jan 26, 2020

Bug

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

pydantic version: 1.4
pydantic compiled: True
install path: /home/**removed**/venv/lib/python3.7/site-packages/pydantic
python version: 3.7.3 (default, Apr  3 2019, 19:16:38)  [GCC 8.0.1 20180414 (experimental) [trunk revision 259383]]
platform: Linux-4.19.86-041986-generic-x86_64-with-Ubuntu-18.04-bionic
     optional deps. installed: []

There is pretty strange behaviour on loading nested list of tuples. I firstly think that this might be intended, but then found out that parse_obj and parse_obj_as give different execution flow which frustrates me.

from pydantic import BaseModel

class OperationData(BaseModel):
    id: str

class Operation(BaseModel):
    __root__: Tuple[int, OperationData]

data = [0, {'id': '1.11.0'}]
# this one works as expected
print(Operation.parse_obj(data))
# printed: __root__=(0, OperationData(id='1.11.0'))

# However, this one doesn't
print(parse_obj_as(Operation, data))
# Traceback (most recent call last):
#  File "/home/**removed**/protocol/base.py", line 238, in <module>
#    print(parse_obj_as(Operation, data))
#  File "pydantic/tools.py", line 35, in pydantic.tools.parse_obj_as
#  File "pydantic/main.py", line 283, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for ParsingModel[Operation]
#__root__
# value is not a valid dict (type=type_error.dict)

# Which is not a big problem. The problem is that I have nested class

class OperationsBatch(BaseModel):
    batch_desc: str
    operations: List[Operation]

# and it produces same exception on
print(OperationsBatch.parse_obj({'batch_desc': '123', 'operations': [data, data]}))

# Traceback (most recent call last):
# File "/home/**removed**/protocol/base.py", line 243, in <module>
#    OperationsBatch.parse_obj({'batch_desc': '123', 'operations': [data, data]})
#  File "pydantic/main.py", line 402, in pydantic.main.BaseModel.parse_obj
#  File "pydantic/main.py", line 283, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 2 validation errors for OperationsBatch
# operations -> 0
# value is not a valid dict (type=type_error.dict)
# operations -> 1
# value is not a valid dict (type=type_error.dict)

It doesn't look like a right behaviour.

@DMantis DMantis added the bug V1 Bug related to Pydantic V1.X label Jan 26, 2020
@samuelcolvin
Copy link
Member

I suspect this is because internally parse_obj_as creates a model with __root__ set to its first argument, so effectively you're doing

from pydantic import BaseModel

class OperationData(BaseModel):
    id: str

class Operation(BaseModel):
    __root__: Tuple[int, OperationData]

class ParseAsObjectModel(BaseModel):
    __root__: Operation

which then fails, should be fixable via either or both:

  1. getting the above to work, even though it looks weird
  2. parse_as_obj detecting a model (perhaps a model with a custom root type) as it's first argument and using that model instead of creating another one.

It occurs to me that 1. might not be possible (or possible without complex or backwards incompatible changes) due to the way we handle custom root types.

The second option therefore might be easier.

@dmontagu
Copy link
Contributor

For what it's worth, the following change seems to make the error go away:
Change:

https://github.com/samuelcolvin/pydantic/blob/e4cd9d2c87b0e6f42366b617de6441f8cd561085/pydantic/main.py#L549-L554

to

        else:
            if cls.__custom_root_type__:
                value_as_dict = {"__root__": value}
            else:
                try:
                    value_as_dict = dict(value)
                except (TypeError, ValueError) as e:
                    raise DictError() from e
            return cls(**value_as_dict)

I don't necessarily think this is the optimal fix, but it might be useful as a starting point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V1 Bug related to Pydantic V1.X
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants