Repository: pydantic/pydantic
Pull request: 🐛 Don't call `repr()` on qualname retrieval
- Closes #6368

Selected Reviewer: @dmontagu

Problem statement:
Super class variables no longer available to subclasses
### Initial Checks

- [X] I confirm that I'm using Pydantic V2 installed directly from the `main` branch, or equivalent

### Description

Hi, I have an issue with accessing variables in a superclass from a base class. I added an example code in the section below.

Whenever I run the code, I get the following error below. I clipped most of the error message to reduce verbosity.
```
...
  File "random.py", line 22, in __repr__
    return f"{__class__.__name__}(number={self.number})"
  File "random.py", line 12, in number
    return self._number
AttributeError: 'DerivedClass' object has no attribute '_number'
```
Was this error intended to happen?

### Example Code

```Python
from pydantic import validate_call

class BaseClass:

    @validate_call
    def __init__(self, number: int) -> None:
        self._number = number

    @property
    def number(self) -> int:
        return self._number


class DerivedClass(BaseClass):

    @validate_call
    def __init__(self, number: int) -> None:
        super().__init__(number)

    def __repr__(self) -> str:
        return f"{__class__.__name__}(number={self.number})"
    
d = DerivedClass(50)
print(d)
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.0
        pydantic-core version: 2.0.1 release build profile
                 install path: /Users/troiw/opt/miniconda3/envs/torch_mdn/lib/python3.8/site-packages/pydantic
               python version: 3.8.16 (default, Jun 12 2023, 12:55:15)  [Clang 14.0.6 ]
                     platform: macOS-10.16-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @Kludex

Hints from issue discussion:
It seems like `__repr__` is getting called one the type itself?

```python
from pydantic import validate_call


class Class:
    @validate_call
    def __init__(self, number: int) -> None:
        assert False, 'init'

    def __repr__(self) -> str:
        assert False, 'repr'

Class(50) #> AssertionError: repr
```

```

Traceback (most recent call last):
  File "/Users/foo/GitHub/pydantic/test.py", line 12, in <module>
    Class(50)
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_validate_call.py", line 76, in __get__
    return self.__class__(bound_function, self._config, self._validate_return)
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_validate_call.py", line 64, in __init__
    self.__pydantic_core_schema__ = schema = gen_schema.collect_definitions(gen_schema.generate_schema(function))
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_generate_schema.py", line 264, in generate_schema
    return self._generate_schema_for_type(
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_generate_schema.py", line 280, in _generate_schema_for_type
    from_property = self._generate_schema_from_property(obj, obj)
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_generate_schema.py", line 414, in _generate_schema_from_property
    with self.defs.get_schema_or_ref(obj) as (_, maybe_schema):
  File "/Users/foo/.pyenv/versions/3.10.9/lib/python3.10/contextlib.py", line 135, in __enter__
    return next(self.gen)
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_generate_schema.py", line 1659, in get_schema_or_ref
    ref = get_type_ref(tp)
  File "/Users/foo/GitHub/pydantic/pydantic/_internal/_core_utils.py", line 86, in get_type_ref
    qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
  File "/Users/foo/GitHub/pydantic/test.py", line 10, in __repr__
    assert False, 'repr'
```
Is this a bug within pydantic? Or is there something I did not do in my code?
Not sure yet. It seems like a bug in Pydantic, but we need to investigate more to confirm.

Commit message: 🐛 Don't call `repr()` on qualname retrieval

Patch to review:
diff --git a/pydantic/_internal/_core_utils.py b/pydantic/_internal/_core_utils.py
index e1c1a74bedc..55f6e9de539 100644
--- a/pydantic/_internal/_core_utils.py
+++ b/pydantic/_internal/_core_utils.py
@@ -83,7 +83,7 @@ def get_type_ref(type_: type[Any], args_override: tuple[type[Any], ...] | None =
     if isinstance(origin, TypeAliasType):
         type_ref = f'{module_name}.{origin.__name__}'
     else:
-        qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
+        qualname = getattr(origin, '__qualname__', '<No __qualname__>')
         type_ref = f'{module_name}.{qualname}:{id(origin)}'
 
     arg_refs: list[str] = []
diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py
index 5fc015dedf6..4220917092f 100644
--- a/tests/test_validate_call.py
+++ b/tests/test_validate_call.py
@@ -637,3 +637,15 @@ def f1(m1: Model1, m2: Model2) -> Tuple[Model1, Model2]:
 
     res = f1({'x': '1'}, {'y': '2'})  # type: ignore
     assert res == ({'x': 1}, Model2(y=2))
+
+
+def test_do_not_call_repr_on_validate_call() -> None:
+    class Class:
+        @validate_call
+        def __init__(self, number: int) -> None:
+            ...
+
+        def __repr__(self) -> str:
+            assert False
+
+    Class(50)


Code review comments:
diff_hunk: @@ -83,7 +83,7 @@ def get_type_ref(type_: type[Any], args_override: tuple[type[Any], ...] | None =
     if isinstance(origin, TypeAliasType):
         type_ref = f'{module_name}.{origin.__name__}'
     else:
-        qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
+        qualname = getattr(origin, '__qualname__', '<No __qualname__>')
path: pydantic/_internal/_core_utils.py
text: @user1:
```suggestion
        try:
            qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
        except Exception:
            qualname = getattr(origin, '__qualname__', '<No __qualname__>')
```
this is what I was suggesting in my comment

@author:
Yeah, this is what I mentioned on https://github.com/pydantic/pydantic/pull/6382#discussion_r1250725725

@author:
(I've edited your msg because it had a f-string, fyi)