Repository: pydantic/pydantic

Issue:
TypeAdapter doesn't work properly with tagged-unions
### Initial Checks

- [X] I confirm that I'm using Pydantic V2 installed directly from the `main` branch, or equivalent

### Description


<details> 
  <summary>I got the next error</summary>

```py
   Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/type_adapter.py", line 154, in __init__
    core_schema = _discriminated_union.apply_discriminators(_core_utils.flatten_schema_defs(core_schema))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 32, in apply_discriminators
    return _core_utils.walk_core_schema(schema, inner)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 392, in walk_core_schema
    return f(schema.copy(), _dispatch)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 211, in handle_definitions_schema
    updated_definition = self.walk(definition, f)
                         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 199, in _handle_other_schemas
    schema['schema'] = self.walk(schema['schema'], f)  # type: ignore
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 316, in handle_model_fields_schema
    replaced_field['schema'] = self.walk(v['schema'], f)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 230, in handle_list_schema
    schema['items_schema'] = self.walk(schema['items_schema'], f)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 29, in inner
    s = apply_discriminator(s, discriminator, definitions)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 61, in apply_discriminator
    return _ApplyInferredDiscriminator(discriminator, definitions or {}).apply(schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 156, in apply
    schema = self._apply_to_root(schema)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 181, in _apply_to_root
    raise TypeError('`discriminator` can only be used with `Union` type with more than one variant')
TypeError: `discriminator` can only be used with `Union` type with more than one variant
```
</details>

### Example Code

```Python
import enum
from typing import Annotated, Literal

from pydantic import BaseModel, Field, TypeAdapter


class ComponentType(enum.IntEnum):
        SomeComponent1 = 1
        SomeComponent2 = 2

class SomeComponent1(BaseModel):
    type: Literal[ComponentType.SomeComponent1] = ComponentType.SomeComponent1
    
class SomeComponent2(BaseModel):
    type: Literal[ComponentType.SomeComponent2] = ComponentType.SomeComponent2

Component = Annotated[SomeComponent1|SomeComponent2, Field(discriminator='type')]

class TestModel(BaseModel):
    components: list[Component]

data = [{
    'components': [
        {
            'type': ComponentType.SomeComponent1.value,
        },
    ],
}]

model = TypeAdapter(list[TestModel])
print(model.validate_python(data))
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.0
        pydantic-core version: 2.0.1 release build profile
                 install path: /workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic
               python version: 3.11.3 (main, Apr 12 2023, 14:31:14) [GCC 10.2.1 20210110]
                     platform: Linux-5.15.0-1041-azure-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @dmontagu


Discussion:
The problem seems to be deeper than that.

There is another case:
```py
import enum
from typing import Annotated, Literal

from pydantic import BaseModel, Field


class ComponentType(enum.IntEnum):
        SomeComponent1 = 1
        SomeComponent2 = 2

class SomeComponent1(BaseModel):
    type: Literal[ComponentType.SomeComponent1] = ComponentType.SomeComponent1
    
class SomeComponent2(BaseModel):
    type: Literal[ComponentType.SomeComponent2] = ComponentType.SomeComponent2

Component = Annotated[SomeComponent1|SomeComponent2, Field(discriminator='type')]

class TestModel1(BaseModel):
    components: list[Component]
    
class TestModel2(BaseModel):
    components: list[Component]
    
class TestModel3(BaseModel):
    submodels: TestModel1|TestModel2
```
<details>
<summary>Traceback</summary>

```py
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 172, in __new__
    complete_model_class(
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 430, in complete_model_class
    schema = apply_discriminators(flatten_schema_defs(schema))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 32, in apply_discriminators
    return _core_utils.walk_core_schema(schema, inner)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 392, in walk_core_schema
    return f(schema.copy(), _dispatch)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 211, in handle_definitions_schema
    updated_definition = self.walk(definition, f)
                         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 199, in _handle_other_schemas
    schema['schema'] = self.walk(schema['schema'], f)  # type: ignore
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 316, in handle_model_fields_schema
    replaced_field['schema'] = self.walk(v['schema'], f)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 25, in inner
    s = recurse(s, inner)
        ^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 191, in _walk
    schema = self._schema_type_to_method[schema['type']](schema, f)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 230, in handle_list_schema
    schema['items_schema'] = self.walk(schema['items_schema'], f)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_core_utils.py", line 188, in walk
    return f(schema.copy(), self._walk)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 29, in inner
    s = apply_discriminator(s, discriminator, definitions)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 61, in apply_discriminator
    return _ApplyInferredDiscriminator(discriminator, definitions or {}).apply(schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 156, in apply
    schema = self._apply_to_root(schema)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/asyncord/.venv/lib/python3.11/site-packages/pydantic/_internal/_discriminated_union.py", line 181, in _apply_to_root
    raise TypeError('`discriminator` can only be used with `Union` type with more than one variant')
TypeError: `discriminator` can only be used with `Union` type with more than one variant
```
</details>


I've found the commit with the broken changes: d97b34504a9a9374298eceb35ed65b4948be2167
Currently, I have no idea how to fix that.

Resolving patch:
diff --git a/pydantic/_internal/_discriminated_union.py b/pydantic/_internal/_discriminated_union.py
--- a/pydantic/_internal/_discriminated_union.py
+++ b/pydantic/_internal/_discriminated_union.py
@@ -23,6 +23,9 @@ def apply_discriminators(schema: core_schema.CoreSchema) -> core_schema.CoreSche
 
     def inner(s: core_schema.CoreSchema, recurse: _core_utils.Recurse) -> core_schema.CoreSchema:
         s = recurse(s, inner)
+        if s['type'] == 'tagged-union':
+            return s
+
         metadata = s.get('metadata', {})
         discriminator = metadata.get(CORE_SCHEMA_METADATA_DISCRIMINATOR_PLACEHOLDER_KEY, None)
         if discriminator is not None:


Test patch:
diff --git a/tests/test_discriminated_union.py b/tests/test_discriminated_union.py
--- a/tests/test_discriminated_union.py
+++ b/tests/test_discriminated_union.py
@@ -1106,3 +1106,113 @@ def test_wrapped_nullable_union() -> None:
             'from_attributes': True,
         },
     }
+
+
+def test_union_in_submodel() -> None:
+    class UnionModel1(BaseModel):
+        type: Literal[1] = 1
+        other: Literal['UnionModel1'] = 'UnionModel1'
+
+    class UnionModel2(BaseModel):
+        type: Literal[2] = 2
+        other: Literal['UnionModel2'] = 'UnionModel2'
+
+    UnionModel = Annotated[Union[UnionModel1, UnionModel2], Field(discriminator='type')]
+
+    class SubModel1(BaseModel):
+        union_model: UnionModel
+
+    class SubModel2(BaseModel):
+        union_model: UnionModel
+
+    class TestModel(BaseModel):
+        submodel: Union[SubModel1, SubModel2]
+
+    m = TestModel.model_validate({'submodel': {'union_model': {'type': 1}}})
+    assert isinstance(m.submodel, SubModel1)
+    assert isinstance(m.submodel.union_model, UnionModel1)
+
+    m = TestModel.model_validate({'submodel': {'union_model': {'type': 2}}})
+    assert isinstance(m.submodel, SubModel1)
+    assert isinstance(m.submodel.union_model, UnionModel2)
+
+    with pytest.raises(ValidationError) as exc_info:
+        TestModel.model_validate({'submodel': {'union_model': {'type': 1, 'other': 'UnionModel2'}}})
+
+    # insert_assert(exc_info.value.errors())
+    assert exc_info.value.errors() == [
+        {
+            'type': 'literal_error',
+            'loc': ('submodel', 'SubModel1', 'union_model', 1, 'other'),
+            'msg': "Input should be 'UnionModel1'",
+            'input': 'UnionModel2',
+            'ctx': {'expected': "'UnionModel1'"},
+            'url': 'https://errors.pydantic.dev/2.0.1/v/literal_error',
+        },
+        {
+            'type': 'literal_error',
+            'loc': ('submodel', 'SubModel2', 'union_model', 1, 'other'),
+            'msg': "Input should be 'UnionModel1'",
+            'input': 'UnionModel2',
+            'ctx': {'expected': "'UnionModel1'"},
+            'url': 'https://errors.pydantic.dev/2.0.1/v/literal_error',
+        },
+    ]
+
+    # insert_assert(TestModel.model_json_schema())
+    assert TestModel.model_json_schema() == {
+        '$defs': {
+            'SubModel1': {
+                'properties': {
+                    'union_model': {
+                        'discriminator': {
+                            'mapping': {'1': '#/$defs/UnionModel1', '2': '#/$defs/UnionModel2'},
+                            'propertyName': 'type',
+                        },
+                        'oneOf': [{'$ref': '#/$defs/UnionModel1'}, {'$ref': '#/$defs/UnionModel2'}],
+                        'title': 'Union Model',
+                    }
+                },
+                'required': ['union_model'],
+                'title': 'SubModel1',
+                'type': 'object',
+            },
+            'SubModel2': {
+                'properties': {
+                    'union_model': {
+                        'discriminator': {
+                            'mapping': {'1': '#/$defs/UnionModel1', '2': '#/$defs/UnionModel2'},
+                            'propertyName': 'type',
+                        },
+                        'oneOf': [{'$ref': '#/$defs/UnionModel1'}, {'$ref': '#/$defs/UnionModel2'}],
+                        'title': 'Union Model',
+                    }
+                },
+                'required': ['union_model'],
+                'title': 'SubModel2',
+                'type': 'object',
+            },
+            'UnionModel1': {
+                'properties': {
+                    'type': {'const': 1, 'default': 1, 'title': 'Type'},
+                    'other': {'const': 'UnionModel1', 'default': 'UnionModel1', 'title': 'Other'},
+                },
+                'title': 'UnionModel1',
+                'type': 'object',
+            },
+            'UnionModel2': {
+                'properties': {
+                    'type': {'const': 2, 'default': 2, 'title': 'Type'},
+                    'other': {'const': 'UnionModel2', 'default': 'UnionModel2', 'title': 'Other'},
+                },
+                'title': 'UnionModel2',
+                'type': 'object',
+            },
+        },
+        'properties': {
+            'submodel': {'anyOf': [{'$ref': '#/$defs/SubModel1'}, {'$ref': '#/$defs/SubModel2'}], 'title': 'Submodel'}
+        },
+        'required': ['submodel'],
+        'title': 'TestModel',
+        'type': 'object',
+    }