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

Discriminated unions raise TypeError for unions of a single type #3636

Closed
3 tasks done
tommilligan opened this issue Jan 7, 2022 · 4 comments · Fixed by #3639
Closed
3 tasks done

Discriminated unions raise TypeError for unions of a single type #3636

tommilligan opened this issue Jan 7, 2022 · 4 comments · Fixed by #3639
Labels
bug V1 Bug related to Pydantic V1.X

Comments

@tommilligan
Copy link
Contributor

tommilligan commented Jan 7, 2022

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this is a bug

Bug

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

             pydantic version: 1.9.0
            pydantic compiled: False
                 install path: /home/tom/reinfer/env/lib/python3.8/site-packages/pydantic
               python version: 3.8.10 (default, Nov 26 2021, 20:14:08)  [GCC 9.3.0]
                     platform: Linux-5.11.0-44-generic-x86_64-with-glibc2.29
     optional deps. installed: ['dotenv', 'typing-extensions']

When a Union with only a single variant it tagged with a field as a discriminated union, a type error is thrown on defining the class. The below is a minimally reproducible example:

# repro.py
from typing import Literal, Union

from pydantic import BaseModel, Field


class DataAModel(BaseModel):
    kind: Literal["a"]


class RequestModel(BaseModel):
    data: Union[DataAModel] = Field(..., discriminator="kind")

produces

$ python repro.py
Traceback (most recent call last):
  File "repro.py", line 10, in <module>
    class RequestModel(BaseModel):
  File "/home/tom/r/env/lib/python3.8/site-packages/pydantic/main.py", line 204, in __new__
    fields[ann_name] = ModelField.infer(
  File "/home/tom/r/env/lib/python3.8/site-packages/pydantic/fields.py", line 488, in infer
    return cls(
  File "/home/tom/r/env/lib/python3.8/site-packages/pydantic/fields.py", line 419, in __init__
    self.prepare()
  File "/home/tom/r/env/lib/python3.8/site-packages/pydantic/fields.py", line 534, in prepare
    self._type_analysis()
  File "/home/tom/r/env/lib/python3.8/site-packages/pydantic/fields.py", line 605, in _type_analysis
    raise TypeError('`discriminator` can only be used with `Union` type')
TypeError: `discriminator` can only be used with `Union` type

The following code snippet works as intended. The only difference is I have introduced a second type in the discriminated union field. Note that simply repeating the existing type (such as Union[DataAModel, DataAModel]) still fails:

from typing import Literal, Union

from pydantic import BaseModel, Field


class DataAModel(BaseModel):
    kind: Literal["a"]


class DataBModel(BaseModel):
    kind: Literal["b"]


class RequestModel(BaseModel):
    data: Union[DataAModel, DataBModel] = Field(..., discriminator="kind")

After some poking, it looks like within _type_analysis, self.discriminator_key is set on the model DataAModel, rather than the parent field, Union[DataAModel]. I'm guessing there is a simplification step somewhere where singleton unions are simplified to their child type, which is no longer accurate?

Happy to submit a PR if you can point me further in the right approach.

@tommilligan tommilligan added the bug V1 Bug related to Pydantic V1.X label Jan 7, 2022
@PrettyWood
Copy link
Member

Hi @tommilligan
The thing is we can't do anything about it because python changes Union[A] into A at interpretation time.

from typing import Union
class A: ...
assert Union[A] is A

So at runtime, pydantic has no way to know A was supposed to be a Union

@tommilligan
Copy link
Contributor Author

Ah, that makes sense. I was about to start poking to see if the simplification was internal to pydantic or not, but if it's at a higher layer I'll give it up as a lost cause.

Would you accept a PR noting this as an edge case in the documentation/error message?

I suppose the workaround is to add a dummy second type into the union, or just to remove the discriminator until it is required.

For documentation forward compatibility, we were using openapi-schema-pydantic, which now collides with the discriminator Field property nicely. But that's not your problem! I'll file a bug over there now.

@PrettyWood
Copy link
Member

Documentation PRs are always welcome :)

@tommilligan
Copy link
Contributor Author

Opened #3639

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.

2 participants