Repository: samuelcolvin/pydantic (python)

Issue:
Self-referenced ClassVar causes TypeError: issubclass() arg 1 must be a class (in 1.9.0, worked in 1.8.2)
### Checks

* [X] I added a descriptive title to this issue
* [X] I have searched (google, github) for similar issues and couldn't find anything -> Issue #545 is similar, but this case is a regression in 1.9.
* [X] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

<!-- Sorry to sound so draconian, but every second saved replying to issues is time spend improving pydantic :-) -->

# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
pydantic version: 1.9.0
pydantic compiled: True
install path: C:\Program Files\Python39\Lib\site-packages\pydantic
python version: 3.9.0 (tags/v3.9.0:9cf6752, Oct  5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)]
platform: Windows-10-10.0.19041-SP0
optional deps. installed: ['email-validator', 'typing-extensions']
```
Also occurs on Linux with Python 3.9.9.


```py
from __future__ import annotations
import pydantic
import typing

class WithClassVar(pydantic.BaseModel):
    Instances : typing.ClassVar[dict[str,WithClassVar]] = {} # fails with issubclass() arg 1 must be a class; arg  1 is typing.ClassVar object 
    # Instances : typing.ClassVar[dict[str,'WithClassVar']] = {} # works
    # instance : dict[str,WithClassVar] = {} # no classvar - works

...
```
I believe this case is specific to a ClassVar referencing itself. The argument passed to `issubclass `is not a class, but an instance of `typing.ClassVar`. 

Call stack:

```
__subclasscheck__ (c:\Program Files\Python39\Lib\typing.py:789)
pydantic.fields.ModelField._type_analysis (c:\Program Files\Python39\Lib\site-packages\pydantic\fields.py:638)
pydantic.fields.ModelField.prepare (c:\Program Files\Python39\Lib\site-packages\pydantic\fields.py:534)
pydantic.typing.update_field_forward_refs (c:\Program Files\Python39\Lib\site-packages\pydantic\typing.py:451)
pydantic.typing.update_model_forward_refs (c:\Program Files\Python39\Lib\site-packages\pydantic\typing.py:480)
pydantic.main.BaseModel.__try_update_forward_refs__ (c:\Program Files\Python39\Lib\site-packages\pydantic\main.py:773)
pydantic.main.ModelMetaclass.__new__ (c:\Program Files\Python39\Lib\site-packages\pydantic\main.py:292)
<module> (.... \pydantic_bug.py:5)
```




Pull request description:
guard against ClassVar in fields
## Change Summary

Allow self referencing `ClassVar`s in models but checking for class vars after forward refs are resolved.

## Related issue number

fix #3679

## Checklist

* [x] Unit tests for the changes exist
* [ ] Tests pass on CI and coverage remains at 100%
* [ ] ~~Documentation reflects the changes where applicable~~
* [x] `changes/<pull request or issue id>-<github username>.md` file added describing change


Patch:
diff --git a/changes/3679-samuelcolvin.md b/changes/3679-samuelcolvin.md
new file mode 100644
index 000000000..ac589451f
--- /dev/null
+++ b/changes/3679-samuelcolvin.md
@@ -0,0 +1,1 @@
+Allow self referencing `ClassVar`s in models but checking for class vars after forward refs are resolved.
diff --git a/pydantic/fields.py b/pydantic/fields.py
index e6736f023..8890ede93 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -37,6 +37,7 @@ from .typing import (
     display_as_type,
     get_args,
     get_origin,
+    is_classvar,
     is_literal_type,
     is_new_type,
     is_none_type,
@@ -611,6 +612,8 @@ class ModelField(Representation):
             return
         elif origin is Callable:
             return
+        elif is_classvar(origin):
+            return
         elif is_union(origin):
             types_ = []
             for type_ in get_args(self.type_):


Test patch:
diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py
index 61e7be5cf..969c3f59b 100644
--- a/tests/test_forward_ref.py
+++ b/tests/test_forward_ref.py
@@ -1,3 +1,4 @@
+import sys
 from typing import Optional, Tuple
 
 import pytest
@@ -669,3 +670,19 @@ class User(BaseModel):
 
     m = module.User(name='anne', friends=[{'name': 'ben'}, {'name': 'charlie'}])
     assert m.json(models_as_dict=False) == '{"name": "anne", "friends": ["User(ben)", "User(charlie)"]}'
+
+
+@pytest.mark.skipif(sys.version_info < (3, 9), reason='needs 3.9 or newer')
+def test_class_var_forward_ref(create_module):
+    # see #3679
+    create_module(
+        # language=Python
+        """
+from __future__ import annotations
+from typing import ClassVar
+from pydantic import BaseModel
+
+class WithClassVar(BaseModel):
+    Instances: ClassVar[dict[str, WithClassVar]] = {}
+"""
+    )