Repository: pydantic/pydantic
Pull request: Add a check on the existence of `__qualname__`
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->

## Change Summary

Add a check on the existence of `__qualname__` in the `namespace` in function `inspect_namespace`, to avoid unexpected `KeyError `when setting a type-type default value in `create_model`, which type is defined in the same module.

## Related issue number

Fix [#8633](https://github.com/pydantic/pydantic/issues/8633)

## Checklist

* [x] The pull request title is a good summary of the changes - it will be used in the changelog
* [x] Unit tests for the changes exist
* [x] Tests pass on CI
* [ ] Documentation reflects the changes where applicable
* [x] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**

Selected Reviewer: @sydney-runkle 

Selected Reviewer: @Kludex

Problem statement:
Unexpected KeyError occured when calling `create_model` with a `type`-type field which is defined in the same module
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

When create a model by calling `create_model`, there is no `__qualname__` in the model's `namespace`. According to the following code in `pydantic._internal._model_construction`, a KeyError will be raised when adding a type-type (defined in the same module) field
```python
# pydantic._internal._model_construction
def inspect_namespace(
    ...
    # line 331
    for var_name, value in list(namespace.items()):
        if var_name == 'model_config':
            continue
        elif (
            isinstance(value, type)
            and value.__module__ == namespace['__module__']
            and value.__qualname__.startswith(namespace['__qualname__'])
        ):
    ...
```
Maybe Adding `namespace["__qualname__"] = __model_name` into `create_model` can easily solve this? I don't have much idea.

### Example Code

```Python
from pydantic import create_model, BaseModel


class A:
    pass

class StaticModel(BaseModel):
    a_cls: type[A] = A  # this works fine


DynamicModel = create_model("DynamicModel", a_cls=(type[A], A))
"""
Traceback (most recent call last):
  File "path\to\test.py", line 8, in <module>
    DynamicModel = create_model("DynamicModel", a_cls=(type[A], A))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\.venv\Lib\site-packages\pydantic\main.py", line 1490, in create_model
    return meta(
           ^^^^^
  File "path\to\.venv\Lib\site-packages\pydantic\_internal\_model_construction.py", line 92, in __new__
    private_attributes = inspect_namespace(
                         ^^^^^^^^^^^^^^^^^^
  File "path\to\.venv\Lib\site-packages\pydantic\_internal\_model_construction.py", line 337, in inspect_namespace
    and value.__qualname__.startswith(namespace['__qualname__'])
                                      ~~~~~~~~~^^^^^^^^^^^^^^^^
KeyError: '__qualname__'
"""
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.6.0b1
        pydantic-core version: 2.16.1
          pydantic-core build: profile=release pgo=true
                 install path: path\to\.venv\Lib\site-packages\pydantic
               python version: 3.11.7 (tags/v3.11.7:fa7a6f2, Dec  4 2023, 19:24:49) [MSC v.1937 64 bit (AMD64)]
                     platform: Windows-10-10.0.22000-SP0
             related packages: fastapi-0.109.0 typing_extensions-4.9.0
                       commit: unknown
```

Hints from issue discussion:
@anci3ntr0ck,

Thanks for reporting this. I think the issue can be solved by simply adding a check on the existence of the `__qualname__` in the namespace, like this:

```py
elif (
    isinstance(value, type)
    and value.__module__ == namespace['__module__']
    and namespace.get('__qualname__')
    and value.__qualname__.startswith(namespace['__qualname__'])
):
```

Would you be interested in opening a PR with a test + this fix? Thanks for bringing this issue to our attention 👍.
@sydney-runkle Sure. Very glad to be a contributor to this great library 😆!
I'll work on this soon 👌.

Commit message: Add a check on the existence of `__qualname__`

Patch to review:
diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py
index 543f73e974d..e41d74ab4bd 100644
--- a/pydantic/_internal/_model_construction.py
+++ b/pydantic/_internal/_model_construction.py
@@ -334,6 +334,7 @@ def inspect_namespace(  # noqa C901
         elif (
             isinstance(value, type)
             and value.__module__ == namespace['__module__']
+            and '__qualname__' in namespace
             and value.__qualname__.startswith(namespace['__qualname__'])
         ):
             # `value` is a nested type defined in this namespace; don't error
diff --git a/tests/test_create_model.py b/tests/test_create_model.py
index fe78db83708..58622c3aea7 100644
--- a/tests/test_create_model.py
+++ b/tests/test_create_model.py
@@ -601,3 +601,10 @@ class Y:
     )
     Z = create_model('Z', y=(module.Y, ...))
     assert Z(y={'x': {}}).y is not None
+
+
+def test_type_field_in_the_same_module():
+    class A:
+        pass
+
+    create_model('B', a_cls=(type, A))


Code review comments:
diff_hunk: @@ -601,3 +601,10 @@ class Y:
     )
     Z = create_model('Z', y=(module.Y, ...))
     assert Z(y={'x': {}}).y is not None
+
+
+def test_type_field_in_the_same_module():
+    class A:
+        pass
+
+    create_model('B', a_cls=(type, A))
path: tests/test_create_model.py
text: @user1:
```suggestion
    B = create_model('B', a_cls=(type, A))
    b = B()
    assert b.a_cls == A
```