Repository: samuelcolvin/pydantic
Pull request #4282 (2022-07-26 17:31:58)

Use `pip` directly to install FastAPI in its integration test
## Change Summary

This change results in:

1. Slightly reduced CI time
2. Allowance for FastAPI to use a different build backend if they wish. For example, moving away from Flit would introduce the possibility of using plugins that could source the version from Git, compile code with Mypyc, etc.

## Related issue number

N/A

## Checklist

* [X] Unit tests for the changes exist
* [X] Tests pass on CI and coverage remains at 100%
* [X] Documentation reflects the changes where applicable
* [ ] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)
* [X] My PR is ready to review, **please add a comment including the phrase "please review" to assign reviewers**



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 240a25bce..d997845c1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -103,7 +103,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.7', '3.8', '3.9', '3.10']
+        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11.0-beta.5']
     env:
       PYTHON: ${{ matrix.python-version }}
       OS: ubuntu
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 2b0afc67c..31bc2b90d 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,12 +1,12 @@
-ansi2html==1.6.0
-flake8==4.0.1
+ansi2html==1.8.0
+flake8==5.0.3
 flake8-quotes==3.3.1
-hypothesis==6.48.2
-markdown-include==0.6.0
-mdx-truly-sane-lists==1.2
+hypothesis==6.53.0
+markdown-include==0.7.0
+mdx-truly-sane-lists==1.3
 mkdocs==1.3.1
 mkdocs-exclude==1.0.2
-mkdocs-material==8.3.8
+mkdocs-material==8.3.9
 sqlalchemy
 orjson
 ujson
diff --git a/pydantic/schema.py b/pydantic/schema.py
index d37e82206..4f29bd5c7 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -667,13 +667,11 @@ def enum_process_schema(enum: Type[Enum], *, field: Optional[ModelField] = None)
 
     This is similar to the `model_process_schema` function, but applies to ``Enum`` objects.
     """
-    from inspect import getdoc
-
     schema_: Dict[str, Any] = {
         'title': enum.__name__,
         # Python assigns all enums a default docstring value of 'An enumeration', so
         # all enums will have a description field even if not explicitly provided.
-        'description': getdoc(enum),
+        'description': enum.__doc__ or 'An enumeration.',
         # Add enum values and the enum field type to the schema.
         'enum': [item.value for item in cast(Iterable[Enum], enum)],
     }
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 44fba3610..5a70a593a 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -132,6 +132,15 @@ else:
         """
         if hasattr(tp, '_nparams'):
             return (Any,) * tp._nparams
+        # Special case for `tuple[()]`, which used to return ((),) with `typing.Tuple`
+        # in python 3.10- but now returns () for `tuple` and `Tuple`.
+        # This will probably be clarified in pydantic v2
+        try:
+            if tp == Tuple[()] or sys.version_info >= (3, 9) and tp == tuple[()]:  # type: ignore[misc]
+                return ((),)
+        # there is a TypeError when compiled with cython
+        except TypeError:  # pragma: no cover
+            pass
         return ()
 
     def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
diff --git a/pydantic/validators.py b/pydantic/validators.py
index d4783d97b..2c6565802 100644
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -698,7 +698,7 @@ def find_validators(  # noqa: C901 (ignore complexity)
 
     class_ = get_class(type_)
     if class_ is not None:
-        if isinstance(class_, type):
+        if class_ is not Any and isinstance(class_, type):
             yield make_class_validator(class_)
         else:
             yield any_class_validator
diff --git a/requirements.txt b/requirements.txt
index 05e48dfcc..88fbe6955 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 # requirements for compilation and from setup.py so dependabot prompts us to test with latest version of these packages
 
-Cython==0.29.30;sys_platform!='win32'
-devtools==0.8.0
+Cython==0.29.32;sys_platform!='win32'
+devtools==0.9.0
 email-validator==1.2.1
 dataclasses==0.6; python_version < '3.7'
 typing-extensions==4.3.0
diff --git a/setup.cfg b/setup.cfg
index ed77c38ea..a3fb54b9e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,6 +8,9 @@ filterwarnings =
     # for Python 3.10+: mypy still relies on distutils on windows. We hence ignore those warnings
     ignore:The distutils package is deprecated and slated for removal in Python 3.12:DeprecationWarning
     ignore:The distutils.sysconfig module is deprecated, use sysconfig instead:DeprecationWarning
+    # for Python 3.11
+    ignore:path is deprecated.*:DeprecationWarning:certifi
+    ignore:module 'sre_constants' is deprecated:DeprecationWarning:pkg_resources
 
 [flake8]
 max-line-length = 120


Test patch:
diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt
index b8e2452c8..50de9c43c 100644
--- a/tests/requirements-linting.txt
+++ b/tests/requirements-linting.txt
@@ -1,11 +1,11 @@
 black==22.6.0
-flake8==4.0.1
+flake8==5.0.3
 flake8-quotes==3.3.1
-hypothesis==6.48.2
+hypothesis==6.53.0
 isort==5.10.1
-pyupgrade==2.34.0
+pyupgrade==2.37.3
 mypy==0.950
-pre-commit==2.19.0
-pycodestyle==2.8.0
-pyflakes==2.4.0
+pre-commit==2.20.0
+pycodestyle==2.9.0
+pyflakes==2.5.0
 twine==4.0.1
diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt
index 09b8b13a8..0f8ac5d6f 100644
--- a/tests/requirements-testing.txt
+++ b/tests/requirements-testing.txt
@@ -1,9 +1,9 @@
-coverage==6.4.1
-hypothesis==6.48.2
+coverage==6.4.2
+hypothesis==6.53.0
 # pin importlib-metadata as upper versions need typing-extensions to work if on Python < 3.8
 importlib-metadata==3.1.0;python_version<"3.8"
 mypy==0.950
 pytest==7.1.2
 pytest-cov==3.0.0
-pytest-mock==3.8.1
-pytest-sugar==0.9.4
+pytest-mock==3.8.2
+pytest-sugar==0.9.5
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index b7083d058..4b6c056c1 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -14,6 +14,7 @@ from pydantic import (
     NoneStrBytes,
     StrBytes,
     ValidationError,
+    compiled,
     constr,
     errors,
     validate_model,
@@ -236,6 +237,97 @@ def test_tuple_more():
     }
 
 
+@pytest.mark.parametrize(
+    'dict_cls,frozenset_cls,list_cls,set_cls,tuple_cls,type_cls',
+    [
+        (Dict, FrozenSet, List, Set, Tuple, Type),
+        (dict, frozenset, list, set, tuple, type),
+    ],
+)
+@pytest.mark.skipif(
+    sys.version_info < (3, 9) or compiled, reason='PEP585 generics only supported for python 3.9 and above'
+)
+def test_pep585_generic_types(dict_cls, frozenset_cls, list_cls, set_cls, tuple_cls, type_cls):
+    class Type1:
+        pass
+
+    class Type2:
+        pass
+
+    class Model(BaseModel, arbitrary_types_allowed=True):
+        a: dict_cls
+        a1: dict_cls[str, int]
+        b: frozenset_cls
+        b1: frozenset_cls[int]
+        c: list_cls
+        c1: list_cls[int]
+        d: set_cls
+        d1: set_cls[int]
+        e: tuple_cls
+        e1: tuple_cls[int]
+        e2: tuple_cls[int, ...]
+        e3: tuple_cls[()]
+        f: type_cls
+        f1: type_cls[Type1]
+
+    default_model_kwargs = dict(
+        a={},
+        a1={'a': '1'},
+        b=[],
+        b1=('1',),
+        c=[],
+        c1=('1',),
+        d=[],
+        d1=['1'],
+        e=[],
+        e1=['1'],
+        e2=['1', '2'],
+        e3=[],
+        f=Type1,
+        f1=Type1,
+    )
+
+    m = Model(**default_model_kwargs)
+    assert m.a == {}
+    assert m.a1 == {'a': 1}
+    assert m.b == frozenset()
+    assert m.b1 == frozenset({1})
+    assert m.c == []
+    assert m.c1 == [1]
+    assert m.d == set()
+    assert m.d1 == {1}
+    assert m.e == ()
+    assert m.e1 == (1,)
+    assert m.e2 == (1, 2)
+    assert m.e3 == ()
+    assert m.f == Type1
+    assert m.f1 == Type1
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(**(default_model_kwargs | {'e3': (1,)}))
+    assert exc_info.value.errors() == [
+        {
+            'ctx': {'actual_length': 1, 'expected_length': 0},
+            'loc': ('e3',),
+            'msg': 'wrong tuple length 1, expected 0',
+            'type': 'value_error.tuple.length',
+        }
+    ]
+
+    Model(**(default_model_kwargs | {'f': Type2}))
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(**(default_model_kwargs | {'f1': Type2}))
+    assert exc_info.value.errors() == [
+        {
+            'ctx': {'expected_class': 'Type1'},
+            'loc': ('f1',),
+            'msg': 'subclass of Type1 expected',
+            'type': 'type_error.subclass',
+        }
+    ]
+
+
 def test_tuple_length_error():
     class Model(BaseModel):
         v: Tuple[int, float, bool]
diff --git a/tests/test_fastapi.sh b/tests/test_fastapi.sh
index ff5a3e3ad..26fcc2174 100755
--- a/tests/test_fastapi.sh
+++ b/tests/test_fastapi.sh
@@ -9,7 +9,6 @@ latest_tag_commit=$(git rev-list --tags --max-count=1)
 latest_tag=$(git describe --tags "${latest_tag_commit}")
 git checkout "${latest_tag}"
 
-pip install -U flit
-flit install
+pip install .[all,dev,doc,test]
 
 ./scripts/test.sh