Repository: samuelcolvin/pydantic
Pull request #1838 (2020-08-16 22:16:31)

Fix representation of literal enums (#1747)
## Change Summary

Fix for Literal enum values does not respect the use_enum_values config

## Related issue number

#1747

## 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
* [x] `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)


codecov[bot]: # [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=h1) Report
> Merging [#1838](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=desc) into [master](https://codecov.io/gh/samuelcolvin/pydantic/commit/44616e35cb0ddd97af07b95d7209176a29bfea49&el=desc) will **decrease** coverage by `0.77%`.
> The diff coverage is `n/a`.


```diff
@@             Coverage Diff             @@
##            master    #1838      +/-   ##
===========================================
- Coverage   100.00%   99.22%   -0.78%     
===========================================
  Files           21       21              
  Lines         3895     3894       -1     
  Branches       783      783              
===========================================
- Hits          3895     3864      -31     
- Misses           0       23      +23     
- Partials         0        7       +7     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `79.24% <0.00%> (-20.76%)` | :arrow_down: |
| [pydantic/version.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvdmVyc2lvbi5weQ==) | `88.23% <0.00%> (-11.77%)` | :arrow_down: |
| [pydantic/env\_settings.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvZW52X3NldHRpbmdzLnB5) | `97.40% <0.00%> (-2.60%)` | :arrow_down: |
| [pydantic/utils.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvdXRpbHMucHk=) | `99.21% <0.00%> (-0.79%)` | :arrow_down: |
| [pydantic/networks.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvbmV0d29ya3MucHk=) | `99.22% <0.00%> (-0.78%)` | :arrow_down: |
| [pydantic/decorator.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838/diff?src=pr&el=tree#diff-cHlkYW50aWMvZGVjb3JhdG9yLnB5) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=footer). Last update [44616e3...85e22df](https://codecov.io/gh/samuelcolvin/pydantic/pull/1838?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

noelevans: > I still don't know what is the expected behaviour with `Literal` and `use_enum_values`.
> We could have simply changed `make_literal_validator` into
> 
> ```python
> def make_literal_validator(type_: Any, config: Type['BaseConfig']) -> Callable[[Any], Any]:
>     permitted_choices = all_literal_values(type_)
>     allowed_choices_set = set(permitted_choices)
> 
>     def literal_validator(v: Any) -> Any:
>         if v not in allowed_choices_set:
>             raise errors.WrongConstantError(given=v, permitted=permitted_choices)
>         return v.value if isinstance(v, Enum) and config.use_enum_values else v
> 
>     return literal_validator
> ```
> 
> but it would have meant that both fields in model
> 
> ```python
>     class Model(BaseModel):
>         baz: Literal[FooEnum.foo]
>         boo: FooEnum = FooEnum.foo
> 
>         class Config:
>             use_enum_values = True
> ```
> 
> would be considered the same.
> With your implementation, they are equal only for dumping (`dict()`)

Ok, I see. Perhaps @samuelcolvin or someone else could give an opinion on the best way to proceed?

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52084de03..67d4ad8d9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,13 +9,41 @@ on:
   pull_request: {}
 
 jobs:
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: set up python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.9
+
+      - name: install
+        run: |
+          make install-linting
+          pip freeze
+
+      - name: lint
+        run: make lint
+
+      - name: mypy
+        run: make mypy
+
+      - name: make history
+        run: python3 ./changes/make_history.py
+
+      - name: check dist
+        run: make check-dist
+
   test-linux:
     name: test py${{ matrix.python-version }} on ubuntu
     runs-on: ubuntu-latest
     strategy:
       fail-fast: false
       matrix:
-        python-version: ['3.6', '3.7', '3.8']
+        python-version: ['3.6', '3.7', '3.8', '3.9']
     env:
       PYTHON: ${{ matrix.python-version }}
       OS: ubuntu
@@ -28,26 +56,14 @@ jobs:
       with:
         python-version: ${{ matrix.python-version }}
 
-    - name: install dependencies
+    - name: install
       run: |
-        make install
+        make install-testing
         pip freeze
 
-    - name: lint
-      run: make lint
-
-    - name: mypy
-      run: make mypy
-
-    - name: make history
-      run: python3 ./changes/make_history.py
-
-    - name: check dist
-      run: make check-dist
-
     - name: compile
       run: |
-        make build-cython-trace
+        make build-trace
         python -c "import sys, pydantic; print('compiled:', pydantic.compiled); sys.exit(0 if pydantic.compiled else 1)"
         ls -alh
         ls -alh pydantic/
@@ -103,7 +119,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [macos, windows]
-        python-version: ['3.6', '3.7', '3.8']
+        python-version: ['3.6', '3.7', '3.8', '3.9']
     env:
       PYTHON: ${{ matrix.python-version }}
       OS: ${{ matrix.os }}
@@ -120,13 +136,7 @@ jobs:
         python-version: ${{ matrix.python-version }}
 
     - name: install
-      run: make install
-
-    - name: lint
-      run: make lint
-
-    - name: mypy
-      run: make mypy
+      run: make install-testing
 
     - name: test
       run: make test
@@ -149,7 +159,11 @@ jobs:
       with:
         python-version: '3.7'
 
-    - run: make test-fastapi
+    - name: install
+      run: make install-testing
+
+    - name: test
+      run: make test-fastapi
 
   benchmark:
     name: run benchmarks
@@ -163,13 +177,12 @@ jobs:
     - name: set up python
       uses: actions/setup-python@v2
       with:
-        python-version: '3.7'
+        python-version: '3.8'
 
     - name: install and build
       run: |
-        make install
-        make build-cython
-        pip install -r benchmarks/requirements.txt
+        make build
+        make install-benchmarks
 
     - run: make benchmark-pydantic
     - run: make benchmark-all
@@ -177,7 +190,7 @@ jobs:
 
   build:
     name: build py3.${{ matrix.python-version }} on ${{ matrix.platform || matrix.os }}
-    needs: [test-linux, test-windows-mac, test-fastapi]
+    needs: [lint, test-linux, test-windows-mac, test-fastapi]
     if: "success() && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master')"
     strategy:
       fail-fast: false
@@ -190,6 +203,7 @@ jobs:
         - '6'
         - '7'
         - '8'
+        - '9'
         include:
         - os: ubuntu
           platform: linux
@@ -206,12 +220,10 @@ jobs:
         python-version: '3.8'
 
     - name: install
-      run: |
-        make install
-        pip install -U setuptools wheel cibuildwheel
+      run: pip install -U twine setuptools wheel cibuildwheel
 
     - name: build sdist
-      if: matrix.os == 'ubuntu' && matrix.python-version == '8'
+      if: matrix.os == 'ubuntu' && matrix.python-version == '9'
       run: python setup.py sdist bdist_wheel
       env:
         SKIP_CYTHON: 1
@@ -260,14 +272,20 @@ jobs:
         uses: actions/download-artifact@v2
         with:
           name: pypi_files
+          path: dist
 
       - name: install
-        run: make install
+        run: |
+          make install-docs
+          pip install -U twine
 
       - name: twine check
         run: |
-          mv pypi_files dist
           twine check dist/*
+          ls -lh dist
+
+      - name: build docs
+        run: make docs
 
       - name: check tag
         run: ./tests/check_tag.py
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index ffe99070e..bb2d6c8e4 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -14,7 +14,7 @@ jobs:
         python-version: 3.8
 
     - name: install
-      run: make install
+      run: make install-docs
 
     - name: build site
       run: |
diff --git a/.gitignore b/.gitignore
index 9a5c4556f..b653170b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@ venv/
 env36/
 env37/
 env38/
+Pipfile
+*.lock
 *.py[cod]
 *.egg-info/
 .python-version
diff --git a/Makefile b/Makefile
index 0417adfe9..64913b92a 100644
--- a/Makefile
+++ b/Makefile
@@ -2,18 +2,38 @@
 isort = isort pydantic tests
 black = black -S -l 120 --target-version py38 pydantic tests
 
-.PHONY: install
-install:
-	python -m pip install -U setuptools pip
-	pip install -U -r requirements.txt
+.PHONY: install-linting
+install-linting:
+	pip install -r tests/requirements-linting.txt
+
+.PHONY: install-pydantic
+install-pydantic:
+	python -m pip install -U wheel pip
+	pip install -r requirements.txt
 	SKIP_CYTHON=1 pip install -e .
 
-.PHONY: build-cython-trace
-build-cython-trace:
+.PHONY: install-testing
+install-testing: install-pydantic
+	pip install -r tests/requirements-testing.txt
+
+.PHONY: install-docs
+install-docs: install-pydantic
+	pip install -U -r docs/requirements.txt
+
+.PHONY: install-benchmarks
+install-benchmarks: install-pydantic
+	pip install -U -r benchmarks/requirements.txt
+
+.PHONY: install
+install: install-linting install-testing
+	@echo 'installed development requirements'
+
+.PHONY: build-trace
+build-trace:
 	python setup.py build_ext --force --inplace --define CYTHON_TRACE
 
-.PHONY: build-cython
-build-cython:
+.PHONY: build
+build:
 	python setup.py build_ext --inplace
 
 .PHONY: format
@@ -48,7 +68,7 @@ testcov: test
 	@coverage html
 
 .PHONY: testcov-compile
-testcov-compile: build-cython-trace test
+testcov-compile: build-trace test
 	@echo "building coverage html"
 	@coverage html
 
@@ -57,6 +77,11 @@ test-examples:
 	@echo "running examples"
 	@find docs/examples -type f -name '*.py' | xargs -I'{}' sh -c 'python {} >/dev/null 2>&1 || (echo "{} failed")'
 
+.PHONY: test-fastapi
+test-fastapi:
+	git clone https://github.com/tiangolo/fastapi.git
+	./tests/test_fastapi.sh
+
 .PHONY: all
 all: lint mypy testcov
 
@@ -108,14 +133,7 @@ docs-serve:
 	mkdocs serve
 
 .PHONY: publish-docs
-publish-docs: docs
+publish-docs:
 	zip -r site.zip site
-	@curl -H "Content-Type: application/zip" -H "Authorization: Bearer ${NETLIFY}" \
+	@curl -f -H "Content-Type: application/zip" -H "Authorization: Bearer ${NETLIFY}" \
 	      --data-binary "@site.zip" https://api.netlify.com/api/v1/sites/pydantic-docs.netlify.com/deploys
-
-fastapi:
-	git clone https://github.com/tiangolo/fastapi.git
-
-.PHONY: test-fastapi
-test-fastapi: install fastapi
-	./tests/test_fastapi.sh
diff --git a/benchmarks/requirements.txt b/benchmarks/requirements.txt
index aaebf1237..9033b1762 100644
--- a/benchmarks/requirements.txt
+++ b/benchmarks/requirements.txt
@@ -1,4 +1,5 @@
 # no versions since benchmarks should always run with the most recent version of each package
+devtools
 
 python-dateutil
 trafaret
diff --git a/changes/1479-kilo59.md b/changes/1479-kilo59.md
new file mode 100644
index 000000000..0a56b435a
--- /dev/null
+++ b/changes/1479-kilo59.md
@@ -0,0 +1,1 @@
+Support `ref_template` when creating schema `$ref`s
diff --git a/changes/1686-MrMrRobat.md b/changes/1686-MrMrRobat.md
new file mode 100644
index 000000000..6e2448f98
--- /dev/null
+++ b/changes/1686-MrMrRobat.md
@@ -0,0 +1,2 @@
+Change default value of ``__module__`` argument of ``create_model`` from ``None`` to ``'pydantic.main'``. 
+Set reference of created concrete model to it's module to allow pickling (not applied to models created in functions)
\ No newline at end of file
diff --git a/changes/1732-PrettyWood.md b/changes/1732-PrettyWood.md
new file mode 100644
index 000000000..7905fa88d
--- /dev/null
+++ b/changes/1732-PrettyWood.md
@@ -0,0 +1,3 @@
+Add `default_factory` support with `BaseModel.construct`.
+Please note that **`__field_defaults__` has been removed**.
+Use `.get_default()` method on fields in `__fields__` attribute instead.
\ No newline at end of file
diff --git a/changes/1735-PrettyWood.md b/changes/1735-PrettyWood.md
new file mode 100644
index 000000000..6d7a68bcf
--- /dev/null
+++ b/changes/1735-PrettyWood.md
@@ -0,0 +1,1 @@
+add `Enum` and `IntEnum` as valid types for fields
\ No newline at end of file
diff --git a/changes/1738-sirtelemak.md b/changes/1738-sirtelemak.md
new file mode 100644
index 000000000..a46ca07a2
--- /dev/null
+++ b/changes/1738-sirtelemak.md
@@ -0,0 +1,1 @@
+Add stub `__init__` with python 3.6 signature for `ForwardRef`
diff --git a/changes/1744-noelevans.md b/changes/1744-noelevans.md
new file mode 100644
index 000000000..5ceab9cd0
--- /dev/null
+++ b/changes/1744-noelevans.md
@@ -0,0 +1,2 @@
+Allows timezone information to be added to strings to be formatted as time objects. Permitted formats are `Z` for UTC 
+or an offset for absolute positive or negative time shifts. Or the timezone data can be omitted.
diff --git a/changes/1747-noelevans.md b/changes/1747-noelevans.md
new file mode 100644
index 000000000..9d100acf3
--- /dev/null
+++ b/changes/1747-noelevans.md
@@ -0,0 +1,1 @@
+Properly represent `Literal` Enums when `use_enum_values` is True.
diff --git a/changes/1748-PrettyWood.md b/changes/1748-PrettyWood.md
new file mode 100644
index 000000000..e2d366e07
--- /dev/null
+++ b/changes/1748-PrettyWood.md
@@ -0,0 +1,1 @@
+Support custom title, description and default in schema of enums
\ No newline at end of file
diff --git a/changes/1832-PrettyWood.md b/changes/1832-PrettyWood.md
new file mode 100644
index 000000000..c468d2c49
--- /dev/null
+++ b/changes/1832-PrettyWood.md
@@ -0,0 +1,1 @@
+add basic support for python 3.9
diff --git a/changes/2020-samuelcolvin.md b/changes/2020-samuelcolvin.md
new file mode 100644
index 000000000..24e6e1304
--- /dev/null
+++ b/changes/2020-samuelcolvin.md
@@ -0,0 +1,1 @@
+Rearrange CI to run linting as a separate job, split install recipes for different tasks.
diff --git a/docs/build/exec_examples.py b/docs/build/exec_examples.py
index 2f167f527..396c424d7 100755
--- a/docs/build/exec_examples.py
+++ b/docs/build/exec_examples.py
@@ -207,6 +207,13 @@ def exec_examples():
         else:
             lines = lines[ignore_above + 1 :]
 
+        try:
+            ignore_below = lines.index('# ignore-below')
+        except ValueError:
+            pass
+        else:
+            lines = lines[:ignore_below]
+
         lines = '\n'.join(lines).split('\n')
         if any(len(l) > MAX_LINE_LENGTH for l in lines):
             error(f'lines longer than {MAX_LINE_LENGTH} characters')
diff --git a/docs/examples/models_parse.py b/docs/examples/models_parse.py
index 8de6a037d..004ca08d6 100644
--- a/docs/examples/models_parse.py
+++ b/docs/examples/models_parse.py
@@ -1,5 +1,7 @@
 import pickle
 from datetime import datetime
+from pathlib import Path
+
 from pydantic import BaseModel, ValidationError
 
 
@@ -31,8 +33,10 @@ m = User.parse_raw(
 )
 print(m)
 
-path = 'data.json'
-with open(path, 'w') as f:
-    f.write('{"id": 123, "name": "James"}')
+path = Path('data.json')
+path.write_text('{"id": 123, "name": "James"}')
 m = User.parse_file(path)
 print(m)
+# ignore-below
+if path.exists():
+    path.unlink()
diff --git a/docs/examples/types_literal1.py b/docs/examples/types_literal1.py
index c44a2eab7..1cc952c5d 100644
--- a/docs/examples/types_literal1.py
+++ b/docs/examples/types_literal1.py
@@ -1,4 +1,4 @@
-from typing_extensions import Literal
+from typing import Literal
 
 from pydantic import BaseModel, ValidationError
 
diff --git a/docs/examples/types_literal2.py b/docs/examples/types_literal2.py
index f0bd12743..6a44c68ae 100644
--- a/docs/examples/types_literal2.py
+++ b/docs/examples/types_literal2.py
@@ -1,6 +1,6 @@
 from typing import ClassVar, List, Union
 
-from typing_extensions import Literal
+from typing import Literal
 
 from pydantic import BaseModel, ValidationError
 
diff --git a/docs/examples/types_literal3.py b/docs/examples/types_literal3.py
index 48fc11745..f783c3900 100644
--- a/docs/examples/types_literal3.py
+++ b/docs/examples/types_literal3.py
@@ -1,6 +1,6 @@
 from typing import Optional, Union
 
-from typing_extensions import Literal
+from typing import Literal
 
 from pydantic import BaseModel
 
diff --git a/docs/install.md b/docs/install.md
index 6346ad0e6..b9bcb4d00 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -1,10 +1,10 @@
 Installation is as simple as:
 
-```py
+```bash
 pip install pydantic
 ```
 
-*pydantic* has no required dependencies except python 3.6, 3.7, or 3.8 (and the dataclasses package in python 3.6).
+*pydantic* has no required dependencies except python 3.6, 3.7, 3.8, or 3.9 (and the dataclasses package for python 3.6).
 If you've got python 3.6+ and `pip` installed, you're good to go.
 
 Pydantic is also available on [conda](https://www.anaconda.com) under the [conda-forge](https://conda-forge.org)
@@ -15,10 +15,10 @@ conda install pydantic -c conda-forge
 ```
 
 *pydantic* can optionally be compiled with [cython](https://cython.org/) which should give a 30-50% performance
-improvement. `manylinux` binaries exist for python 3.6, 3.7, and 3.8, so if you're installing from PyPI on linux, you
-should get a compiled version of *pydantic* with no extra work. If you're installing manually, install `cython`
-before installing *pydantic* and compilation should happen automatically. Compilation with cython
-[is not tested](https://github.com/samuelcolvin/pydantic/issues/555) on windows or mac.
+improvement. 
+
+Binaries are available from [PyPI](https://pypi.org/project/pydantic/#files) for Linux, MacOS and 64bit Windows.
+If you're installing manually, install `cython` before installing *pydantic* and compilation should happen automatically.
 
 To test if *pydantic* is compiled run:
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 91b6322f1..1ce891d0b 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,8 +1,10 @@
 ansi2html==1.5.2
+flake8==3.8.4
+flake8-quotes==3.2.0
 mkdocs==1.1.2
-markdown==3.3
+markdown==3.3.1
 mkdocs-exclude==1.0.2
-mkdocs-material==6.0.2
+mkdocs-material==6.1.0
 markdown-include==0.6.0
 pygments==2.7.1
 sqlalchemy   # pyup: ignore
diff --git a/docs/usage/schema.md b/docs/usage/schema.md
index dff86bbc1..f2f889df8 100644
--- a/docs/usage/schema.md
+++ b/docs/usage/schema.md
@@ -33,6 +33,10 @@ the `Field` class.
 The schema is generated by default using aliases as keys, but it can be generated using model
 property names instead by calling `MainModel.schema/schema_json(by_alias=False)`.
 
+The format of `$ref`s (`"#/definitions/FooBar"` above) can be altered by calling `schema()` or `schema_json()`
+with the `ref_template` keyword argument, e.g. `ApplePie.schema(ref_template='/schemas/{model}.json#/')`, here `{model}`
+will be replaced with the model naming using `str.format()`.
+
 ## Field customisation
 
 Optionally, the `Field` function can be used to provide extra information about the field and validations.
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 9f30ab6f3..316c6a2cb 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -130,10 +130,16 @@ with custom properties and validation.
   see [Pydantic Types](#pydantic-types) for other custom IP address types
 
 `enum.Enum`
+: checks that the value is a valid Enum instance
+
+`subclass of enum.Enum`
 : checks that the value is a valid member of the enum;
   see [Enums and Choices](#enums-and-choices) for more details
 
 `enum.IntEnum`
+: checks that the value is a valid IntEnum instance
+
+`subclass of enum.IntEnum`
 : checks that the value is a valid member of the integer enum;
   see [Enums and Choices](#enums-and-choices) for more details
 
@@ -251,7 +257,7 @@ types:
     * `int` or `float`, assumed as Unix time, i.e. seconds (if >= `-2e10` or <= `2e10`) or milliseconds (if < `-2e10`or > `2e10`) since 1 January 1970
     * `str`, following formats work:
 
-        * `YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z[±]HH[:]MM]]]`
+        * `YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]]]`
         * `int` or `float` as a string (assumed as Unix time)
 
 * `date` fields can be:
@@ -268,7 +274,7 @@ types:
     * `time`, existing `time` object
     * `str`, following formats work:
 
-        * `HH:MM[:SS[.ffffff]]`
+        * `HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]]]`
 
 * `timedelta` fields can be:
 
diff --git a/pydantic/datetime_parse.py b/pydantic/datetime_parse.py
index 1bdc13a3a..d567c5c51 100644
--- a/pydantic/datetime_parse.py
+++ b/pydantic/datetime_parse.py
@@ -16,23 +16,21 @@ Changed to:
 """
 import re
 from datetime import date, datetime, time, timedelta, timezone
-from typing import Dict, Union
+from typing import Dict, Optional, Type, Union
 
 from . import errors
 
-date_re = re.compile(r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$')
-
-time_re = re.compile(
-    r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
-)
-
-datetime_re = re.compile(
-    r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
-    r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
+date_expr = r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
+time_expr = (
+    r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
     r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
     r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
 )
 
+date_re = re.compile(f'{date_expr}$')
+time_re = re.compile(time_expr)
+datetime_re = re.compile(f'{date_expr}[T ]{time_expr}')
+
 standard_duration_re = re.compile(
     r'^'
     r'(?:(?P<days>-?\d+) (days?, )?)?'
@@ -81,6 +79,22 @@ def from_unix_seconds(seconds: Union[int, float]) -> datetime:
     return dt.replace(tzinfo=timezone.utc)
 
 
+def _parse_timezone(value: Optional[str], error: Type[Exception]) -> Union[None, int, timezone]:
+    if value == 'Z':
+        return timezone.utc
+    elif value is not None:
+        offset_mins = int(value[-2:]) if len(value) > 3 else 0
+        offset = 60 * int(value[1:3]) + offset_mins
+        if value[0] == '-':
+            offset = -offset
+        try:
+            return timezone(timedelta(minutes=offset))
+        except ValueError:
+            raise error()
+    else:
+        return None
+
+
 def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
     """
     Parse a date/int/float/string and return a datetime.date.
@@ -117,8 +131,6 @@ def parse_time(value: Union[time, StrBytesIntFloat]) -> time:
     """
     Parse a time/string and return a datetime.time.
 
-    This function doesn't support time zone offsets.
-
     Raise ValueError if the input is well formatted but not a valid time.
     Raise ValueError if the input isn't well formatted, in particular if it contains an offset.
     """
@@ -143,7 +155,9 @@ def parse_time(value: Union[time, StrBytesIntFloat]) -> time:
     if kw['microsecond']:
         kw['microsecond'] = kw['microsecond'].ljust(6, '0')
 
-    kw_ = {k: int(v) for k, v in kw.items() if v is not None}
+    tzinfo = _parse_timezone(kw.pop('tzinfo'), errors.TimeError)
+    kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
+    kw_['tzinfo'] = tzinfo
 
     try:
         return time(**kw_)  # type: ignore
@@ -179,19 +193,8 @@ def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
     if kw['microsecond']:
         kw['microsecond'] = kw['microsecond'].ljust(6, '0')
 
-    tzinfo_str = kw.pop('tzinfo')
-    if tzinfo_str == 'Z':
-        tzinfo = timezone.utc
-    elif tzinfo_str is not None:
-        offset_mins = int(tzinfo_str[-2:]) if len(tzinfo_str) > 3 else 0
-        offset = 60 * int(tzinfo_str[1:3]) + offset_mins
-        if tzinfo_str[0] == '-':
-            offset = -offset
-        tzinfo = timezone(timedelta(minutes=offset))
-    else:
-        tzinfo = None
-
-    kw_: Dict[str, Union[int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
+    tzinfo = _parse_timezone(kw.pop('tzinfo'), errors.DateTimeError)
+    kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
     kw_['tzinfo'] = tzinfo
 
     try:
diff --git a/pydantic/errors.py b/pydantic/errors.py
index 1abb9d8ad..d996bb341 100644
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -30,6 +30,8 @@ __all__ = (
     'UrlPortError',
     'UrlExtraError',
     'EnumError',
+    'IntEnumError',
+    'EnumMemberError',
     'IntegerError',
     'FloatError',
     'PathError',
@@ -216,7 +218,9 @@ class UrlExtraError(UrlError):
     msg_template = 'URL invalid, extra characters found after valid URL: {extra!r}'
 
 
-class EnumError(PydanticTypeError):
+class EnumMemberError(PydanticTypeError):
+    code = 'enum'
+
     def __str__(self) -> str:
         permitted = ', '.join(repr(v.value) for v in self.enum_values)  # type: ignore
         return f'value is not a valid enumeration member; permitted: {permitted}'
@@ -492,6 +496,16 @@ class CallableError(PydanticTypeError):
     msg_template = '{value} is not callable'
 
 
+class EnumError(PydanticTypeError):
+    code = 'enum_instance'
+    msg_template = '{value} is not a valid Enum instance'
+
+
+class IntEnumError(PydanticTypeError):
+    code = 'int_enum_instance'
+    msg_template = '{value} is not a valid IntEnum instance'
+
+
 class IPvAnyAddressError(PydanticValueError):
     msg_template = 'value is not a valid IPv4 or IPv6 address'
 
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 2619b4b16..d9bd85613 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -31,6 +31,8 @@ from .typing import (
     NoArgAnyCallable,
     NoneType,
     display_as_type,
+    get_args,
+    get_origin,
     is_literal_type,
     is_new_type,
     new_type_supertype,
@@ -390,7 +392,7 @@ class ModelField(Representation):
         elif lenient_issubclass(self.type_, Json):
             self.type_ = Any
             self.parse_json = True
-        elif isinstance(self.type_, TypeVar):  # type: ignore
+        elif isinstance(self.type_, TypeVar):
             if self.type_.__bound__:
                 self.type_ = self.type_.__bound__
             elif self.type_.__constraints__:
@@ -411,7 +413,7 @@ class ModelField(Representation):
         elif is_literal_type(self.type_):
             return
 
-        origin = getattr(self.type_, '__origin__', None)
+        origin = get_origin(self.type_)
         if origin is None:
             # field is not "typing" object eg. Union, Dict, List etc.
             # allow None for virtual superclasses of NoneType, e.g. Hashable
@@ -422,7 +424,7 @@ class ModelField(Representation):
             return
         if origin is Union:
             types_ = []
-            for type_ in self.type_.__args__:
+            for type_ in get_args(self.type_):
                 if type_ is NoneType:
                     if self.required is Undefined:
                         self.required = False
@@ -444,9 +446,9 @@ class ModelField(Representation):
         if issubclass(origin, Tuple):  # type: ignore
             self.shape = SHAPE_TUPLE
             self.sub_fields = []
-            for i, t in enumerate(self.type_.__args__):
+            for i, t in enumerate(get_args(self.type_)):
                 if t is Ellipsis:
-                    self.type_ = self.type_.__args__[0]
+                    self.type_ = get_args(self.type_)[0]
                     self.shape = SHAPE_TUPLE_ELLIPSIS
                     return
                 self.sub_fields.append(self._create_sub_type(t, f'{self.name}_{i}'))
@@ -460,7 +462,7 @@ class ModelField(Representation):
                     {f'list_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_LIST
         elif issubclass(origin, Set):
             # Create self validators
@@ -470,22 +472,22 @@ class ModelField(Representation):
                     {f'set_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
                 )
 
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SET
         elif issubclass(origin, FrozenSet):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_FROZENSET
         elif issubclass(origin, Sequence):
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
         elif issubclass(origin, Mapping):
-            self.key_field = self._create_sub_type(self.type_.__args__[0], 'key_' + self.name, for_keys=True)
-            self.type_ = self.type_.__args__[1]
+            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
+            self.type_ = get_args(self.type_)[1]
             self.shape = SHAPE_MAPPING
         # Equality check as almost everything inherits form Iterable, including str
         # check for Iterable and CollectionsIterable, as it could receive one even when declared with the other
         elif origin in {Iterable, CollectionsIterable}:
-            self.type_ = self.type_.__args__[0]
+            self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_ITERABLE
             self.sub_fields = [self._create_sub_type(self.type_, f'{self.name}_type')]
         elif issubclass(origin, Type):  # type: ignore
@@ -494,7 +496,7 @@ class ModelField(Representation):
             # Is a Pydantic-compatible generic that handles itself
             # or we have arbitrary_types_allowed = True
             self.shape = SHAPE_GENERIC
-            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(self.type_.__args__)]
+            self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(get_args(self.type_))]
             self.type_ = origin
             return
         else:
diff --git a/pydantic/generics.py b/pydantic/generics.py
index 64562227d..f8bb7fadc 100644
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -1,8 +1,24 @@
-from typing import TYPE_CHECKING, Any, ClassVar, Dict, Tuple, Type, TypeVar, Union, cast, get_type_hints
+import sys
+from types import FrameType, ModuleType
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    ClassVar,
+    Dict,
+    Optional,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+    cast,
+    get_type_hints,
+)
 
 from .class_validators import gather_all_validators
 from .fields import FieldInfo, ModelField
 from .main import BaseModel, create_model
+from .typing import get_origin
 from .utils import lenient_issubclass
 
 _generic_types_cache: Dict[Tuple[Type[Any], Union[Any, Tuple[Any, ...]]], Type[BaseModel]] = {}
@@ -29,7 +45,7 @@ class GenericModel(BaseModel):
             raise TypeError('Cannot parameterize a concrete instantiation of a generic model')
         if not isinstance(params, tuple):
             params = (params,)
-        if cls is GenericModel and any(isinstance(param, TypeVar) for param in params):  # type: ignore
+        if cls is GenericModel and any(isinstance(param, TypeVar) for param in params):
             raise TypeError('Type parameters should be placed on typing.Generic, not GenericModel')
         if not hasattr(cls, '__parameters__'):
             raise TypeError(f'Type {cls.__name__} must inherit from typing.Generic before being parameterized')
@@ -37,7 +53,7 @@ class GenericModel(BaseModel):
         check_parameters_count(cls, params)
         typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params))
         type_hints = get_type_hints(cls).items()
-        instance_type_hints = {k: v for k, v in type_hints if getattr(v, '__origin__', None) is not ClassVar}
+        instance_type_hints = {k: v for k, v in type_hints if get_origin(v) is not ClassVar}
         concrete_type_hints: Dict[str, Type[Any]] = {
             k: resolve_type_hint(v, typevars_map) for k, v in instance_type_hints.items()
         }
@@ -45,17 +61,27 @@ class GenericModel(BaseModel):
         model_name = cls.__concrete_name__(params)
         validators = gather_all_validators(cls)
         fields = _build_generic_fields(cls.__fields__, concrete_type_hints, typevars_map)
+        model_module = get_caller_module_name() or cls.__module__
         created_model = cast(
             Type[GenericModel],  # casting ensures mypy is aware of the __concrete__ and __parameters__ attributes
             create_model(
                 model_name,
-                __module__=cls.__module__,
+                __module__=model_module,
                 __base__=cls,
                 __config__=None,
                 __validators__=validators,
                 **fields,
             ),
         )
+
+        if is_call_from_module():  # create global reference and therefore allow pickling
+            object_in_module = sys.modules[model_module].__dict__.setdefault(model_name, created_model)
+            if object_in_module is not created_model:
+                # this should not ever happen because of _generic_types_cache, but just in case
+                raise TypeError(f'{model_name!r} already defined above, please consider reusing it') from NameError(
+                    f'Name conflict: {model_name!r} in {model_module!r} is already used by {object_in_module!r}'
+                )
+
         created_model.Config = cls.Config
         concrete = all(not _is_typevar(v) for v in concrete_type_hints.values())
         created_model.__concrete__ = concrete
@@ -79,7 +105,7 @@ class GenericModel(BaseModel):
 
 
 def resolve_type_hint(type_: Any, typevars_map: Dict[Any, Any]) -> Type[Any]:
-    if hasattr(type_, '__origin__') and getattr(type_, '__parameters__', None):
+    if get_origin(type_) and getattr(type_, '__parameters__', None):
         concrete_type_args = tuple([typevars_map[x] for x in type_.__parameters__])
         return type_[concrete_type_args]
     return typevars_map.get(type_, type_)
@@ -113,4 +139,37 @@ def _parameterize_generic_field(field_type: Type[Any], typevars_map: Dict[TypeVa
 
 
 def _is_typevar(v: Any) -> bool:
-    return isinstance(v, TypeVar)  # type: ignore
+    return isinstance(v, TypeVar)
+
+
+def get_caller_module_name() -> Optional[str]:
+    """
+    Used inside a function to get its caller module name
+
+    Will only work against non-compiled code, therefore used only in pydantic.generics
+    """
+    import inspect
+
+    try:
+        previous_caller_frame = inspect.stack()[2].frame
+    except IndexError as e:
+        raise RuntimeError('This function must be used inside another function') from e
+
+    getmodule = cast(Callable[[FrameType, str], Optional[ModuleType]], inspect.getmodule)
+    previous_caller_module = getmodule(previous_caller_frame, previous_caller_frame.f_code.co_filename)
+    return previous_caller_module.__name__ if previous_caller_module is not None else None
+
+
+def is_call_from_module() -> bool:
+    """
+    Used inside a function to check whether it was called globally
+
+    Will only work against non-compiled code, therefore used only in pydantic.generics
+    """
+    import inspect
+
+    try:
+        previous_caller_frame = inspect.stack()[2].frame
+    except IndexError as e:
+        raise RuntimeError('This function must be used inside another function') from e
+    return previous_caller_frame.f_locals is previous_caller_frame.f_globals
diff --git a/pydantic/main.py b/pydantic/main.py
index c6c486ada..d9d4ba34b 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -31,9 +31,9 @@ from .errors import ConfigError, DictError, ExtraError, MissingError
 from .fields import SHAPE_MAPPING, ModelField, Undefined
 from .json import custom_pydantic_encoder, pydantic_encoder
 from .parse import Protocol, load_file, load_str_bytes
-from .schema import model_schema
+from .schema import default_ref_template, model_schema
 from .types import PyObject, StrBytes
-from .typing import AnyCallable, ForwardRef, is_classvar, resolve_annotations, update_field_forward_refs
+from .typing import AnyCallable, ForwardRef, get_origin, is_classvar, resolve_annotations, update_field_forward_refs
 from .utils import (
     ClassAttribute,
     GetterDict,
@@ -215,7 +215,6 @@ class ModelMetaclass(ABCMeta):
         fields: Dict[str, ModelField] = {}
         config = BaseConfig
         validators: 'ValidatorListDict' = {}
-        fields_defaults: Dict[str, Any] = {}
 
         pre_root_validators, post_root_validators = [], []
         for base in reversed(bases):
@@ -231,9 +230,6 @@ class ModelMetaclass(ABCMeta):
         vg = ValidatorGroup(validators)
 
         for f in fields.values():
-            if not f.required:
-                fields_defaults[f.name] = f.default
-
             f.set_config(config)
             extra_validators = vg.get_validators(f.name)
             if extra_validators:
@@ -257,7 +253,7 @@ class ModelMetaclass(ABCMeta):
                     if (
                         isinstance(value, untouched_types)
                         and ann_type != PyObject
-                        and not lenient_issubclass(getattr(ann_type, '__origin__', None), Type)
+                        and not lenient_issubclass(get_origin(ann_type), Type)
                     ):
                         continue
                     fields[ann_name] = inferred = ModelField.infer(
@@ -267,8 +263,6 @@ class ModelMetaclass(ABCMeta):
                         class_validators=vg.get_validators(ann_name),
                         config=config,
                     )
-                    if not inferred.required:
-                        fields_defaults[ann_name] = inferred.default
 
             for var_name, value in namespace.items():
                 if (
@@ -291,8 +285,6 @@ class ModelMetaclass(ABCMeta):
                             f'if you wish to change the type of this field, please use a type annotation'
                         )
                     fields[var_name] = inferred
-                    if not inferred.required:
-                        fields_defaults[var_name] = inferred.default
 
         _custom_root_type = ROOT_KEY in fields
         if _custom_root_type:
@@ -307,7 +299,6 @@ class ModelMetaclass(ABCMeta):
         new_namespace = {
             '__config__': config,
             '__fields__': fields,
-            '__field_defaults__': fields_defaults,
             '__validators__': vg.validators,
             '__pre_root_validators__': unique_list(pre_root_validators + pre_rv_new),
             '__post_root_validators__': unique_list(post_root_validators + post_rv_new),
@@ -327,7 +318,6 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
     if TYPE_CHECKING:
         # populated by the metaclass, defined here to help IDEs only
         __fields__: Dict[str, ModelField] = {}
-        __field_defaults__: Dict[str, Any] = {}
         __validators__: Dict[str, AnyCallable] = {}
         __pre_root_validators__: List[AnyCallable]
         __post_root_validators__: List[Tuple[bool, AnyCallable]]
@@ -528,7 +518,10 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
         Default values are respected, but no other validation is performed.
         """
         m = cls.__new__(cls)
-        object.__setattr__(m, '__dict__', {**smart_deepcopy(cls.__field_defaults__), **values})
+        # default field values
+        fields_values = {name: field.get_default() for name, field in cls.__fields__.items() if not field.required}
+        fields_values.update(values)
+        object.__setattr__(m, '__dict__', fields_values)
         if _fields_set is None:
             _fields_set = set(values.keys())
         object.__setattr__(m, '__fields_set__', _fields_set)
@@ -569,19 +562,23 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
         return m
 
     @classmethod
-    def schema(cls, by_alias: bool = True) -> 'DictStrAny':
-        cached = cls.__schema_cache__.get(by_alias)
+    def schema(cls, by_alias: bool = True, ref_template: str = default_ref_template) -> 'DictStrAny':
+        cached = cls.__schema_cache__.get((by_alias, ref_template))
         if cached is not None:
             return cached
-        s = model_schema(cls, by_alias=by_alias)
-        cls.__schema_cache__[by_alias] = s
+        s = model_schema(cls, by_alias=by_alias, ref_template=ref_template)
+        cls.__schema_cache__[(by_alias, ref_template)] = s
         return s
 
     @classmethod
-    def schema_json(cls, *, by_alias: bool = True, **dumps_kwargs: Any) -> str:
+    def schema_json(
+        cls, *, by_alias: bool = True, ref_template: str = default_ref_template, **dumps_kwargs: Any
+    ) -> str:
         from .json import pydantic_encoder
 
-        return cls.__config__.json_dumps(cls.schema(by_alias=by_alias), default=pydantic_encoder, **dumps_kwargs)
+        return cls.__config__.json_dumps(
+            cls.schema(by_alias=by_alias, ref_template=ref_template), default=pydantic_encoder, **dumps_kwargs
+        )
 
     @classmethod
     def __get_validators__(cls) -> 'CallableGenerator':
@@ -675,6 +672,9 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
                 and (not value_include or value_include.is_included(i))
             )
 
+        elif isinstance(v, Enum) and getattr(cls.Config, 'use_enum_values', False):
+            return v.value
+
         else:
             return v
 
@@ -718,7 +718,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass):
             if (
                 (allowed_keys is not None and field_key not in allowed_keys)
                 or (exclude_none and v is None)
-                or (exclude_defaults and self.__field_defaults__.get(field_key, _missing) == v)
+                or (exclude_defaults and getattr(self.__fields__.get(field_key), 'default', _missing) == v)
             ):
                 continue
             if by_alias and field_key in self.__fields__:
@@ -803,7 +803,7 @@ def create_model(
     *,
     __config__: Type[BaseConfig] = None,
     __base__: Type[BaseModel] = None,
-    __module__: Optional[str] = None,
+    __module__: str = __name__,
     __validators__: Dict[str, classmethod] = None,
     **field_definitions: Any,
 ) -> Type[BaseModel]:
@@ -812,8 +812,9 @@ def create_model(
     :param __model_name: name of the created model
     :param __config__: config class to use for the new model
     :param __base__: base class for the new model to inherit from
+    :param __module__: module of the created model
     :param __validators__: a dict of method names and @validator class methods
-    :param **field_definitions: fields of the model (or extra fields if a base is supplied)
+    :param field_definitions: fields of the model (or extra fields if a base is supplied)
         in the format `<name>=(<type>, <default default>)` or `<name>=<default value>, e.g.
         `foobar=(str, ...)` or `foobar=123`, or, for complex use-cases, in the format
         `<name>=<FieldInfo>`, e.g. `foo=Field(default_factory=datetime.utcnow, alias='bar')`
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 15b506678..db470759b 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -56,7 +56,7 @@ from .types import (
     conset,
     constr,
 )
-from .typing import ForwardRef, Literal, is_callable_type, is_literal_type, literal_values
+from .typing import ForwardRef, Literal, get_args, get_origin, is_callable_type, is_literal_type, literal_values
 from .utils import get_model, lenient_issubclass, sequence_like
 
 if TYPE_CHECKING:
@@ -64,7 +64,7 @@ if TYPE_CHECKING:
     from .main import BaseModel  # noqa: F401
 
 default_prefix = '#/definitions/'
-
+default_ref_template = '#/definitions/{model}'
 
 TypeModelOrEnum = Union[Type['BaseModel'], Type[Enum]]
 TypeModelSet = Set[TypeModelOrEnum]
@@ -77,6 +77,7 @@ def schema(
     title: Optional[str] = None,
     description: Optional[str] = None,
     ref_prefix: Optional[str] = None,
+    ref_template: str = default_ref_template,
 ) -> Dict[str, Any]:
     """
     Process a list of models and generate a single JSON Schema with all of them defined in the ``definitions``
@@ -91,11 +92,13 @@ def schema(
       else, e.g. for OpenAPI use ``#/components/schemas/``. The resulting generated schemas will still be at the
       top-level key ``definitions``, so you can extract them from there. But all the references will have the set
       prefix.
+    :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful
+      for references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For
+      a sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``.
     :return: dict with the JSON Schema with a ``definitions`` top-level key including the schema definitions for
       the models and sub-models passed in ``models``.
     """
     clean_models = [get_model(model) for model in models]
-    ref_prefix = ref_prefix or default_prefix
     flat_models = get_flat_models_from_models(clean_models)
     model_name_map = get_model_name_map(flat_models)
     definitions = {}
@@ -106,7 +109,11 @@ def schema(
         output_schema['description'] = description
     for model in clean_models:
         m_schema, m_definitions, m_nested_models = model_process_schema(
-            model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix
+            model,
+            by_alias=by_alias,
+            model_name_map=model_name_map,
+            ref_prefix=ref_prefix,
+            ref_template=ref_template,
         )
         definitions.update(m_definitions)
         model_name = model_name_map[model]
@@ -117,7 +124,10 @@ def schema(
 
 
 def model_schema(
-    model: Union[Type['BaseModel'], Type['DataclassType']], by_alias: bool = True, ref_prefix: Optional[str] = None
+    model: Union[Type['BaseModel'], Type['DataclassType']],
+    by_alias: bool = True,
+    ref_prefix: Optional[str] = None,
+    ref_template: str = default_ref_template,
 ) -> Dict[str, Any]:
     """
     Generate a JSON Schema for one model. With all the sub-models defined in the ``definitions`` top-level
@@ -130,31 +140,58 @@ def model_schema(
       else, e.g. for OpenAPI use ``#/components/schemas/``. The resulting generated schemas will still be at the
       top-level key ``definitions``, so you can extract them from there. But all the references will have the set
       prefix.
+    :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful for
+      references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For a
+      sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``.
     :return: dict with the JSON Schema for the passed ``model``
     """
     model = get_model(model)
-    ref_prefix = ref_prefix or default_prefix
     flat_models = get_flat_models_from_model(model)
     model_name_map = get_model_name_map(flat_models)
     model_name = model_name_map[model]
     m_schema, m_definitions, nested_models = model_process_schema(
-        model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix
+        model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, ref_template=ref_template
     )
     if model_name in nested_models:
         # model_name is in Nested models, it has circular references
         m_definitions[model_name] = m_schema
-        m_schema = {'$ref': ref_prefix + model_name}
+        m_schema = get_schema_ref(model_name, ref_prefix, ref_template, False)
     if m_definitions:
         m_schema.update({'definitions': m_definitions})
     return m_schema
 
 
+def get_field_info_schema(field: ModelField) -> Tuple[Dict[str, Any], bool]:
+    schema_overrides = False
+
+    # If no title is explicitly set, we don't set title in the schema for enums.
+    # The behaviour is the same as `BaseModel` reference, where the default title
+    # is in the definitions part of the schema.
+    schema: Dict[str, Any] = {}
+    if field.field_info.title or not lenient_issubclass(field.type_, Enum):
+        schema['title'] = field.field_info.title or field.alias.title().replace('_', ' ')
+
+    if field.field_info.title:
+        schema_overrides = True
+
+    if field.field_info.description:
+        schema['description'] = field.field_info.description
+        schema_overrides = True
+
+    if not field.required and not field.field_info.const and field.default is not None:
+        schema['default'] = encode_default(field.default)
+        schema_overrides = True
+
+    return schema, schema_overrides
+
+
 def field_schema(
     field: ModelField,
     *,
     by_alias: bool = True,
     model_name_map: Dict[TypeModelOrEnum, str],
     ref_prefix: Optional[str] = None,
+    ref_template: str = default_ref_template,
     known_models: TypeModelSet = None,
 ) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]:
     """
@@ -168,22 +205,13 @@ def field_schema(
     :param model_name_map: used to generate the JSON Schema references to other models included in the definitions
     :param ref_prefix: the JSON Pointer prefix to use for references to other schemas, if None, the default of
       #/definitions/ will be used
+    :param ref_template: Use a ``string.format()`` template for ``$ref`` instead of a prefix. This can be useful for
+      references that cannot be represented by ``ref_prefix`` such as a definition stored in another file. For a
+      sibling json file in a ``/schemas`` directory use ``"/schemas/${model}.json#"``.
     :param known_models: used to solve circular references
     :return: tuple of the schema for this field and additional definitions
     """
-    ref_prefix = ref_prefix or default_prefix
-    schema_overrides = False
-    s = dict(title=field.field_info.title or field.alias.title().replace('_', ' '))
-    if field.field_info.title:
-        schema_overrides = True
-
-    if field.field_info.description:
-        s['description'] = field.field_info.description
-        schema_overrides = True
-
-    if not field.required and not field.field_info.const and field.default is not None:
-        s['default'] = encode_default(field.default)
-        schema_overrides = True
+    s, schema_overrides = get_field_info_schema(field)
 
     validation_schema = get_field_schema_validations(field)
     if validation_schema:
@@ -196,6 +224,7 @@ def field_schema(
         model_name_map=model_name_map,
         schema_overrides=schema_overrides,
         ref_prefix=ref_prefix,
+        ref_template=ref_template,
         known_models=known_models or set(),
     )
     # $ref will only be returned when there are no schema_overrides
@@ -228,6 +257,11 @@ def get_field_schema_validations(field: ModelField) -> Dict[str, Any]:
     a Pydantic ``FieldInfo`` with validation arguments.
     """
     f_schema: Dict[str, Any] = {}
+
+    if lenient_issubclass(field.type_, Enum):
+        # schema is already updated by `enum_process_schema`
+        return f_schema
+
     if lenient_issubclass(field.type_, (str, bytes)):
         for attr_name, t, keyword in _str_types_attrs:
             attr = getattr(field.field_info, attr_name, None)
@@ -362,6 +396,7 @@ def field_type_schema(
     *,
     by_alias: bool,
     model_name_map: Dict[TypeModelOrEnum, str],
+    ref_template: str,
     schema_overrides: bool = False,
     ref_prefix: Optional[str] = None,
     known_models: TypeModelSet,
@@ -375,10 +410,14 @@ def field_type_schema(
     definitions = {}
     nested_models: Set[str] = set()
     f_schema: Dict[str, Any]
-    ref_prefix = ref_prefix or default_prefix
     if field.shape in {SHAPE_LIST, SHAPE_TUPLE_ELLIPSIS, SHAPE_SEQUENCE, SHAPE_SET, SHAPE_FROZENSET, SHAPE_ITERABLE}:
         items_schema, f_definitions, f_nested_models = field_singleton_schema(
-            field, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
+            field,
+            by_alias=by_alias,
+            model_name_map=model_name_map,
+            ref_prefix=ref_prefix,
+            ref_template=ref_template,
+            known_models=known_models,
         )
         definitions.update(f_definitions)
         nested_models.update(f_nested_models)
@@ -391,7 +430,12 @@ def field_type_schema(
         key_field = cast(ModelField, field.key_field)
         regex = getattr(key_field.type_, 'regex', None)
         items_schema, f_definitions, f_nested_models = field_singleton_schema(
-            field, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
+            field,
+            by_alias=by_alias,
+            model_name_map=model_name_map,
+            ref_prefix=ref_prefix,
+            ref_template=ref_template,
+            known_models=known_models,
         )
         definitions.update(f_definitions)
         nested_models.update(f_nested_models)
@@ -407,7 +451,12 @@ def field_type_schema(
         sub_fields = cast(List[ModelField], field.sub_fields)
         for sf in sub_fields:
             sf_schema, sf_definitions, sf_nested_models = field_type_schema(
-                sf, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
+                sf,
+                by_alias=by_alias,
+                model_name_map=model_name_map,
+                ref_prefix=ref_prefix,
+                ref_template=ref_template,
+                known_models=known_models,
             )
             definitions.update(sf_definitions)
             nested_models.update(sf_nested_models)
@@ -423,6 +472,7 @@ def field_type_schema(
             model_name_map=model_name_map,
             schema_overrides=schema_overrides,
             ref_prefix=ref_prefix,
+            ref_template=ref_template,
             known_models=known_models,
         )
         definitions.update(f_definitions)
@@ -442,6 +492,7 @@ def model_process_schema(
     by_alias: bool = True,
     model_name_map: Dict[TypeModelOrEnum, str],
     ref_prefix: Optional[str] = None,
+    ref_template: str = default_ref_template,
     known_models: TypeModelSet = None,
 ) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]:
     """
@@ -453,7 +504,6 @@ def model_process_schema(
     """
     from inspect import getdoc, signature
 
-    ref_prefix = ref_prefix or default_prefix
     known_models = known_models or set()
     if lenient_issubclass(model, Enum):
         model = cast(Type[Enum], model)
@@ -466,7 +516,12 @@ def model_process_schema(
         s['description'] = doc
     known_models.add(model)
     m_schema, m_definitions, nested_models = model_type_schema(
-        model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
+        model,
+        by_alias=by_alias,
+        model_name_map=model_name_map,
+        ref_prefix=ref_prefix,
+        ref_template=ref_template,
+        known_models=known_models,
     )
     s.update(m_schema)
     schema_extra = model.__config__.schema_extra
@@ -485,6 +540,7 @@ def model_type_schema(
     *,
     by_alias: bool,
     model_name_map: Dict[TypeModelOrEnum, str],
+    ref_template: str,
     ref_prefix: Optional[str] = None,
     known_models: TypeModelSet,
 ) -> Tuple[Dict[str, Any], Dict[str, Any], Set[str]]:
@@ -494,7 +550,6 @@ def model_type_schema(
     Take a single ``model`` and generate the schema for its type only, not including additional
     information as title, etc. Also return additional schema definitions, from sub-models.
     """
-    ref_prefix = ref_prefix or default_prefix
     properties = {}
     required = []
     definitions: Dict[str, Any] = {}
@@ -502,7 +557,12 @@ def model_type_schema(
     for k, f in model.__fields__.items():
         try:
             f_schema, f_definitions, f_nested_models = field_schema(
-                f, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
+                f,
+                by_alias=by_alias,
+                model_name_map=model_name_map,
+                ref_prefix=ref_prefix,
+                ref_template=ref_template,
+                known_models=known_models,
             )
         except SkipField as skip:
             warnings.warn(skip.message, UserWarning)
@@ -560,6 +620,7 @@ def field_singleton_sub_fields_schema(
     *,
     by_alias: bool,
     model_name_map: Dict[TypeModelOrEnum, str],
+    ref_template: str,
     schema_overrides: bool = False,
     ref_prefix: Optional[str] = None,
     known_models: TypeModelSet,
@@ -570,7 +631,6 @@ def field_singleton_sub_fields_schema(
     Take a list of Pydantic ``ModelField`` from the declaration of a type with parameters, and generate their
     schema. I.e., fields used as "type parameters", like ``str`` and ``int`` in ``Tuple[str, int]``.
     """
-    ref_prefix = ref_prefix or default_prefix
     definitions = {}
     nested_models: Set[str] = set()
     sub_fields = [sf for sf in sub_fields if sf.include_in_schema()]
@@ -581,6 +641,7 @@ def field_singleton_sub_fields_schema(
             model_name_map=model_name_map,
             schema_overrides=schema_overrides,
             ref_prefix=ref_prefix,
+            ref_template=ref_template,
             known_models=known_models,
         )
     else:
@@ -592,6 +653,7 @@ def field_singleton_sub_fields_schema(
                 model_name_map=model_name_map,
                 schema_overrides=schema_overrides,
                 ref_prefix=ref_prefix,
+                ref_template=ref_template,
                 known_models=known_models,
             )
             definitions.update(sub_definitions)
@@ -651,11 +713,20 @@ def add_field_type_to_schema(field_type: Any, schema: Dict[str, Any]) -> None:
             break
 
 
+def get_schema_ref(name: str, ref_prefix: Optional[str], ref_template: str, schema_overrides: bool) -> Dict[str, Any]:
+    if ref_prefix:
+        schema_ref = {'$ref': ref_prefix + name}
+    else:
+        schema_ref = {'$ref': ref_template.format(model=name)}
+    return {'allOf': [schema_ref]} if schema_overrides else schema_ref
+
+
 def field_singleton_schema(  # noqa: C901 (ignore complexity)
     field: ModelField,
     *,
     by_alias: bool,
     model_name_map: Dict[TypeModelOrEnum, str],
+    ref_template: str,
     schema_overrides: bool = False,
     ref_prefix: Optional[str] = None,
     known_models: TypeModelSet,
@@ -667,7 +738,6 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
     """
     from .main import BaseModel  # noqa: F811
 
-    ref_prefix = ref_prefix or default_prefix
     definitions: Dict[str, Any] = {}
     nested_models: Set[str] = set()
     if field.sub_fields:
@@ -677,6 +747,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
             model_name_map=model_name_map,
             schema_overrides=schema_overrides,
             ref_prefix=ref_prefix,
+            ref_template=ref_template,
             known_models=known_models,
         )
     if field.type_ is Any or field.type_.__class__ == TypeVar:
@@ -695,6 +766,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
                 by_alias=by_alias,
                 model_name_map=model_name_map,
                 ref_prefix=ref_prefix,
+                ref_template=ref_template,
                 known_models=known_models,
             )
         literal_value = values[0]
@@ -703,7 +775,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
 
     if lenient_issubclass(field_type, Enum):
         enum_name = normalize_name(field_type.__name__)
-        f_schema = {'$ref': ref_prefix + enum_name}
+        f_schema, schema_overrides = get_field_info_schema(field)
+        f_schema.update(get_schema_ref(enum_name, ref_prefix, ref_template, schema_overrides))
         definitions[enum_name] = enum_process_schema(field_type)
     else:
         add_field_type_to_schema(field_type, f_schema)
@@ -727,6 +800,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
                 by_alias=by_alias,
                 model_name_map=model_name_map,
                 ref_prefix=ref_prefix,
+                ref_template=ref_template,
                 known_models=known_models,
             )
             definitions.update(sub_definitions)
@@ -734,11 +808,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
             nested_models.update(sub_nested_models)
         else:
             nested_models.add(model_name)
-        schema_ref = {'$ref': ref_prefix + model_name}
-        if not schema_overrides:
-            return schema_ref, definitions, nested_models
-        else:
-            return {'allOf': [schema_ref]}, definitions, nested_models
+        schema_ref = get_schema_ref(model_name, ref_prefix, ref_template, schema_overrides)
+        return schema_ref, definitions, nested_models
 
     raise ValueError(f'Value not declarable with JSON Schema, field: {field}')
 
@@ -806,9 +877,9 @@ def get_annotation_from_field_info(annotation: Any, field_info: FieldInfo, field
             or lenient_issubclass(type_, (ConstrainedList, ConstrainedSet))
         ):
             return type_
-        origin = getattr(type_, '__origin__', None)
+        origin = get_origin(type_)
         if origin is not None:
-            args: Tuple[Any, ...] = type_.__args__
+            args: Tuple[Any, ...] = get_args(type_)
             if any(isinstance(a, ForwardRef) for a in args):
                 # forward refs cause infinite recursion below
                 return type_
diff --git a/pydantic/tools.py b/pydantic/tools.py
index d0d320b5a..19d992b46 100644
--- a/pydantic/tools.py
+++ b/pydantic/tools.py
@@ -31,7 +31,7 @@ T = TypeVar('T')
 
 
 def parse_obj_as(type_: Type[T], obj: Any, *, type_name: Optional[NameFactory] = None) -> T:
-    model_type = _get_parsing_type(type_, type_name=type_name)
+    model_type = _get_parsing_type(type_, type_name=type_name)  # type: ignore[arg-type]
     return model_type(__root__=obj).__root__
 
 
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 070691eeb..d204f8fc0 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -17,6 +17,7 @@ from typing import (  # type: ignore
     Type,
     Union,
     _eval_type,
+    cast,
 )
 
 try:
@@ -29,6 +30,9 @@ if sys.version_info < (3, 7):
     if TYPE_CHECKING:
 
         class ForwardRef:
+            def __init__(self, arg: Any):
+                pass
+
             def _eval_type(self, globalns: Any, localns: Any) -> Any:
                 pass
 
@@ -44,12 +48,20 @@ if sys.version_info < (3, 7):
         return type_._eval_type(globalns, localns)
 
 
-else:
+elif sys.version_info < (3, 9):
 
     def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
         return type_._evaluate(globalns, localns)
 
 
+else:
+
+    def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
+        # Even though it is the right signature for python 3.9, mypy complains with
+        # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast...
+        return cast(Any, type_)._evaluate(globalns, localns, set())
+
+
 if sys.version_info < (3, 7):
     from typing import Callable as Callable
 
@@ -70,8 +82,55 @@ if sys.version_info < (3, 8):
             from typing_extensions import Literal
         except ImportError:
             Literal = None
+
+    def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+        return getattr(t, '__args__', ())
+
+    def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
+        return getattr(t, '__origin__', None)
+
+
 else:
-    from typing import Literal
+    from typing import Literal, get_args as typing_get_args, get_origin as typing_get_origin
+
+    def get_origin(tp: Type[Any]) -> Type[Any]:
+        """
+        We can't directly use `typing.get_origin` since we need a fallback to support
+        custom generic classes like `ConstrainedList`
+        It should be useless once https://github.com/cython/cython/issues/3537 is
+        solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged.
+        """
+        return typing_get_origin(tp) or getattr(tp, '__origin__', None)
+
+    def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """
+        In python 3.9, `typing.Dict`, `typing.List`, ...
+        do have an empty `__args__` by default (instead of the generic ~T for example).
+        In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
+        we retrieve the `_nparams` value that tells us how many parameters it needs.
+        """
+        if hasattr(tp, '_nparams'):
+            return (Any,) * tp._nparams
+        return ()
+
+    def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
+        """Get type arguments with all substitutions performed.
+
+        For unions, basic simplifications used by Union constructor are performed.
+        Examples::
+            get_args(Dict[str, int]) == (str, int)
+            get_args(int) == ()
+            get_args(Union[int, Union[T, int], str][int]) == (int, str)
+            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+            get_args(Callable[[], T][int]) == ([], int)
+        """
+        try:
+            args = typing_get_args(tp)
+        except IndexError:
+            args = ()
+        # the fallback is needed for the same reasons as `get_origin` (see above)
+        return args or getattr(tp, '__args__', ()) or generic_get_args(tp)
+
 
 if TYPE_CHECKING:
     from .fields import ModelField
@@ -115,6 +174,8 @@ __all__ = (
     'CallableGenerator',
     'ReprArgs',
     'CallableGenerator',
+    'get_args',
+    'get_origin',
 )
 
 
@@ -167,16 +228,16 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti
 
 
 def is_callable_type(type_: Type[Any]) -> bool:
-    return type_ is Callable or getattr(type_, '__origin__', None) is Callable
+    return type_ is Callable or get_origin(type_) is Callable
 
 
 if sys.version_info >= (3, 7):
 
     def is_literal_type(type_: Type[Any]) -> bool:
-        return Literal is not None and getattr(type_, '__origin__', None) is Literal
+        return Literal is not None and get_origin(type_) is Literal
 
     def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
-        return type_.__args__
+        return get_args(type_)
 
 
 else:
@@ -217,12 +278,15 @@ def new_type_supertype(type_: Type[Any]) -> Type[Any]:
     return type_
 
 
-def _check_classvar(v: Type[Any]) -> bool:
+def _check_classvar(v: Optional[Type[Any]]) -> bool:
+    if v is None:
+        return False
+
     return v.__class__ == ClassVar.__class__ and (sys.version_info < (3, 7) or getattr(v, '_name', None) == 'ClassVar')
 
 
 def is_classvar(ann_type: Type[Any]) -> bool:
-    return _check_classvar(ann_type) or _check_classvar(getattr(ann_type, '__origin__', None))
+    return _check_classvar(ann_type) or _check_classvar(get_origin(ann_type))
 
 
 def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None:
@@ -243,13 +307,13 @@ def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
     without brackets. Otherwise returns None.
     """
     try:
-        origin = getattr(type_, '__origin__')
+        origin = get_origin(type_)
         if origin is None:  # Python 3.6
             origin = type_
         if issubclass(origin, Type):  # type: ignore
-            if type_.__args__ is None or not isinstance(type_.__args__[0], type):
+            if not get_args(type_) or not isinstance(get_args(type_)[0], type):
                 return True
-            return type_.__args__[0]
-    except AttributeError:
+            return get_args(type_)[0]
+    except (AttributeError, TypeError):
         pass
     return None
diff --git a/pydantic/utils.py b/pydantic/utils.py
index 42a4d30e6..38bce03c6 100644
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -436,10 +436,10 @@ class GetterDict(Representation):
         return item in self.keys()
 
     def __eq__(self, other: Any) -> bool:
-        return dict(self) == dict(other.items())  # type: ignore
+        return dict(self) == dict(other.items())
 
     def __repr_args__(self) -> 'ReprArgs':
-        return [(None, dict(self))]  # type: ignore
+        return [(None, dict(self))]
 
     def __repr_name__(self) -> str:
         return f'GetterDict[{display_as_type(self._obj)}]'
diff --git a/pydantic/validators.py b/pydantic/validators.py
index a53c83119..515ffd02e 100644
--- a/pydantic/validators.py
+++ b/pydantic/validators.py
@@ -245,12 +245,12 @@ def frozenset_validator(v: Any) -> FrozenSet[Any]:
         raise errors.FrozenSetError()
 
 
-def enum_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum:
+def enum_member_validator(v: Any, field: 'ModelField', config: 'BaseConfig') -> Enum:
     try:
         enum_v = field.type_(v)
     except ValueError:
         # field.type_ should be an enum, so will be iterable
-        raise errors.EnumError(enum_values=list(field.type_))
+        raise errors.EnumMemberError(enum_values=list(field.type_))
     return enum_v.value if config.use_enum_values else enum_v
 
 
@@ -405,6 +405,20 @@ def callable_validator(v: Any) -> AnyCallable:
     raise errors.CallableError(value=v)
 
 
+def enum_validator(v: Any) -> Enum:
+    if isinstance(v, Enum):
+        return v
+
+    raise errors.EnumError(value=v)
+
+
+def int_enum_validator(v: Any) -> IntEnum:
+    if isinstance(v, IntEnum):
+        return v
+
+    raise errors.IntEnumError(value=v)
+
+
 def make_literal_validator(type_: Any) -> Callable[[Any], Any]:
     permitted_choices = all_literal_values(type_)
     allowed_choices_set = set(permitted_choices)
@@ -502,8 +516,8 @@ class IfConfig:
 # order is important here, for example: bool is a subclass of int so has to come first, datetime before date same,
 # IPv4Interface before IPv4Address, etc
 _VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [
-    (IntEnum, [int_validator, enum_validator]),
-    (Enum, [enum_validator]),
+    (IntEnum, [int_validator, enum_member_validator]),
+    (Enum, [enum_member_validator]),
     (
         str,
         [
@@ -565,6 +579,12 @@ def find_validators(  # noqa: C901 (ignore complexity)
     if is_literal_type(type_):
         yield make_literal_validator(type_)
         return
+    if type_ is Enum:
+        yield enum_validator
+        return
+    if type_ is IntEnum:
+        yield int_enum_validator
+        return
 
     class_ = get_class(type_)
     if class_ is not None:
diff --git a/pydantic/version.py b/pydantic/version.py
index 5634efb84..466c0b725 100644
--- a/pydantic/version.py
+++ b/pydantic/version.py
@@ -1,6 +1,6 @@
 __all__ = 'VERSION', 'version_info'
 
-VERSION = '1.6.1'
+VERSION = '1.7a1'
 
 
 def version_info() -> str:
diff --git a/requirements.txt b/requirements.txt
index 006d6e2bc..ee9342558 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,4 @@
-# benchmark requirements are skipped here since they're rarely required
-# -r benchmarks/requirements.txt
--r docs/requirements.txt
--r tests/requirements.txt
+# requirements for compilation and from setup.py so dependabot prompts us to test with latest version of these packages
 
 Cython==0.29.21;sys_platform!='win32'
 devtools==0.6.0
diff --git a/setup.cfg b/setup.cfg
index 732c297e2..37311f84f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,9 +15,6 @@ per-file-ignores =
     docs/examples/validation_decorator_async.py: E402
     docs/examples/types_constrained.py: F722
 
-[bdist_wheel]
-python-tag = py36.py37.py38
-
 [coverage:run]
 source = pydantic
 branch = True
diff --git a/setup.py b/setup.py
index 755d90931..3171cb77c 100644
--- a/setup.py
+++ b/setup.py
@@ -78,7 +78,7 @@ if not any(arg in sys.argv for arg in ['clean', 'check']) and 'SKIP_CYTHON' not
     except ImportError:
         pass
     else:
-        # For cython test coverage install with `make build-cython-trace`
+        # For cython test coverage install with `make build-trace`
         compiler_directives = {}
         if 'CYTHON_TRACE' in sys.argv:
             compiler_directives['linetrace'] = True
@@ -105,6 +105,7 @@ setup(
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
         'Intended Audience :: Developers',
         'Intended Audience :: Information Technology',
         'Intended Audience :: System Administrators',


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index ee61bc8df..098df7a02 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,10 +1,38 @@
+import inspect
 import os
 import secrets
+import subprocess
+import sys
+import textwrap
 from importlib.machinery import SourceFileLoader
+from types import FunctionType
 
 import pytest
 
 
+def _extract_source_code_from_function(function):
+    if function.__code__.co_argcount:
+        raise RuntimeError(f'function {function.__qualname__} cannot have any arguments')
+
+    code_lines = ''
+    body_started = False
+    for line in textwrap.dedent(inspect.getsource(function)).split('\n'):
+        if line.startswith('def '):
+            body_started = True
+            continue
+        elif body_started:
+            code_lines += f'{line}\n'
+
+    return textwrap.dedent(code_lines)
+
+
+def _create_module(code, tmp_path, name):
+    name = f'{name}_{secrets.token_hex(5)}'
+    path = tmp_path / f'{name}.py'
+    path.write_text(code)
+    return name, path
+
+
 class SetEnv:
     def __init__(self):
         self.envars = set()
@@ -28,11 +56,37 @@ def env():
 
 
 @pytest.fixture
-def create_module(tmp_path):
-    def run(code):
-        name = f'test_code_{secrets.token_hex(5)}'
-        path = tmp_path / f'{name}.py'
-        path.write_text(code)
+def create_module(tmp_path, request):
+    def run(module_source_code=None):
+        """
+        Creates module and loads it with SourceFileLoader().load_module()
+        """
+        if isinstance(module_source_code, FunctionType):
+            module_source_code = _extract_source_code_from_function(module_source_code)
+        name, path = _create_module(module_source_code, tmp_path, request.node.name)
         return SourceFileLoader(name, str(path)).load_module()
 
     return run
+
+
+@pytest.fixture
+def run_as_module(tmp_path, request):
+    def run(module_source_code=None):
+        """
+        Creates module source and runs it in subprocess
+
+        This way is much slower than SourceFileLoader().load_module(),
+        but executes module as __main__ with a clear stack (https://docs.python.org/3/library/__main__.html)
+        """
+        if isinstance(module_source_code, FunctionType):
+            module_source_code = _extract_source_code_from_function(module_source_code)
+        _, path = _create_module(module_source_code, tmp_path, request.node.name)
+        result = subprocess.run([sys.executable, str(path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5)
+        if result.returncode != 0:
+            pytest.fail(
+                f'Running {path} failed with non-zero return code: {result.returncode}\n'
+                f'Captured stdout:\n{result.stdout.decode()}\n'
+                f'Captured stderr:\n{result.stderr.decode()}'
+            )
+
+    return run
diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py
index 0ba4ae962..202184c93 100644
--- a/tests/mypy/modules/success.py
+++ b/tests/mypy/modules/success.py
@@ -11,6 +11,7 @@ from typing import Any, Dict, Generic, List, Optional, TypeVar
 from pydantic import BaseModel, NoneStr, PyObject, StrictBool, root_validator, validate_arguments, validator
 from pydantic.fields import Field
 from pydantic.generics import GenericModel
+from pydantic.typing import ForwardRef
 
 
 class Flags(BaseModel):
@@ -129,6 +130,13 @@ foo(1, c='thing')
 foo(1)
 
 
+class Foo(BaseModel):
+    a: int
+
+
+FooRef = ForwardRef('Foo')
+
+
 class MyConf(BaseModel):
     str_pyobject: PyObject = Field('datetime.date')
     callable_pyobject: PyObject = Field(date)
diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt
new file mode 100644
index 000000000..336553d16
--- /dev/null
+++ b/tests/requirements-linting.txt
@@ -0,0 +1,8 @@
+black==20.8b1
+flake8==3.8.4
+flake8-quotes==3.2.0
+isort==5.6.4
+mypy==0.790
+pycodestyle==2.6.0
+pyflakes==2.2.0
+twine==3.2.0
diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt
new file mode 100644
index 000000000..31ea534ef
--- /dev/null
+++ b/tests/requirements-testing.txt
@@ -0,0 +1,6 @@
+coverage==5.3
+mypy==0.790
+pytest==6.1.1
+pytest-cov==2.10.1
+pytest-mock==3.3.1
+pytest-sugar==0.9.4
diff --git a/tests/requirements.txt b/tests/requirements.txt
deleted file mode 100644
index b7a012b01..000000000
--- a/tests/requirements.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-black==20.8b1
-coverage==5.3
-flake8==3.8.4
-flake8-quotes==3.2.0
-isort==5.6.1
-mypy==0.782
-pycodestyle==2.6.0
-pyflakes==2.2.0
-pytest==6.1.1
-pytest-cov==2.10.1
-pytest-mock==3.3.1
-pytest-sugar==0.9.4
-twine==3.2.0
diff --git a/tests/test_construction.py b/tests/test_construction.py
index c60b7f719..93abb6e03 100644
--- a/tests/test_construction.py
+++ b/tests/test_construction.py
@@ -3,7 +3,7 @@ from typing import Any, List
 
 import pytest
 
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 
 
 class Model(BaseModel):
@@ -275,3 +275,13 @@ def test_shallow_copy_modify():
     # deep['deep_thing'] gets modified
     assert x.deep['deep_thing'] == [1, 2, 3]
     assert y.deep['deep_thing'] == [1, 2, 3]
+
+
+def test_construct_default_factory():
+    class Model(BaseModel):
+        foo: List[int] = Field(default_factory=list)
+        bar: str = 'Baz'
+
+    m = Model.construct()
+    assert m.foo == []
+    assert m.bar == 'Baz'
diff --git a/tests/test_create_model.py b/tests/test_create_model.py
index ba8fb44d2..f9f2862a1 100644
--- a/tests/test_create_model.py
+++ b/tests/test_create_model.py
@@ -11,6 +11,7 @@ def test_create_model():
     assert model.__fields__.keys() == {'foo', 'bar'}
     assert model.__validators__ == {}
     assert model.__config__.__name__ == 'Config'
+    assert model.__module__ == 'pydantic.main'
 
 
 def test_create_model_usage():
@@ -24,6 +25,29 @@ def test_create_model_usage():
         model(foo='hello', bar='xxx')
 
 
+def test_create_model_pickle(create_module):
+    """
+    Pickle will work for dynamically created model only if it was defined globally with its class name
+    and module where it's defined was specified
+    """
+
+    @create_module
+    def module():
+        import pickle
+
+        from pydantic import create_model
+
+        FooModel = create_model('FooModel', foo=(str, ...), bar=123, __module__=__name__)
+
+        m = FooModel(foo='hello')
+        d = pickle.dumps(m)
+        m2 = pickle.loads(d)
+        assert m2.foo == m.foo == 'hello'
+        assert m2.bar == m.bar == 123
+        assert m2 == m
+        assert m2 is not m
+
+
 def test_invalid_name():
     with pytest.warns(RuntimeWarning):
         model = create_model('FooModel', _foo=(str, ...))
diff --git a/tests/test_datetime_parse.py b/tests/test_datetime_parse.py
index 20cba7b2b..d629d9fb8 100644
--- a/tests/test_datetime_parse.py
+++ b/tests/test_datetime_parse.py
@@ -65,12 +65,20 @@ def test_date_parsing(value, result):
         (3610, time(1, 0, 10)),
         (3600.5, time(1, 0, 0, 500000)),
         (86400 - 1, time(23, 59, 59)),
+        ('11:05:00-05:30', time(11, 5, 0, tzinfo=create_tz(-330))),
+        ('11:05:00-0530', time(11, 5, 0, tzinfo=create_tz(-330))),
+        ('11:05:00Z', time(11, 5, 0, tzinfo=timezone.utc)),
+        ('11:05:00+00', time(11, 5, 0, tzinfo=timezone.utc)),
+        ('11:05-06', time(11, 5, 0, tzinfo=create_tz(-360))),
+        ('11:05+06', time(11, 5, 0, tzinfo=create_tz(360))),
         # Invalid inputs
         (86400, errors.TimeError),
         ('xxx', errors.TimeError),
         ('091500', errors.TimeError),
         (b'091500', errors.TimeError),
         ('09:15:90', errors.TimeError),
+        ('11:05:00Y', errors.TimeError),
+        ('11:05:00-25:00', errors.TimeError),
     ],
 )
 def test_time_parsing(value, result):
@@ -108,6 +116,7 @@ def test_time_parsing(value, result):
         # Invalid inputs
         ('x20120423091500', errors.DateTimeError),
         ('2012-04-56T09:15:90', errors.DateTimeError),
+        ('2012-04-23T11:05:00-25:00', errors.DateTimeError),
         (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)),  # just before watershed
         (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)),  # just after watershed
         (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)),  # nowish in s
diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py
index 420079dfb..c6431a731 100644
--- a/tests/test_forward_ref.py
+++ b/tests/test_forward_ref.py
@@ -472,3 +472,15 @@ class Filter(BaseModel):
     )
     Filter = module.Filter
     assert isinstance(Filter(p={'sort': 'some_field:asc', 'fields': []}), Filter)
+
+
+def test_forward_ref_with_create_model(create_module):
+    @create_module
+    def module():
+        import pydantic
+
+        Sub = pydantic.create_model('Sub', foo='bar', __module__=__name__)
+        assert Sub  # get rid of "local variable 'Sub' is assigned to but never used"
+        Main = pydantic.create_model('Main', sub=('Sub', ...), __module__=__name__)
+        instance = Main(sub={})
+        assert instance.sub.dict() == {'foo': 'bar'}
diff --git a/tests/test_generics.py b/tests/test_generics.py
index 452fe3bb0..77c76d40d 100644
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -5,7 +5,7 @@ from typing import Any, ClassVar, Dict, Generic, List, Optional, Tuple, Type, Ty
 import pytest
 
 from pydantic import BaseModel, Field, ValidationError, root_validator, validator
-from pydantic.generics import GenericModel, _generic_types_cache
+from pydantic.generics import GenericModel, _generic_types_cache, get_caller_module_name
 
 skip_36 = pytest.mark.skipif(sys.version_info < (3, 7), reason='generics only supported for python 3.7 and above')
 
@@ -412,7 +412,6 @@ def test_custom_schema():
 
 @skip_36
 def test_child_schema():
-
     T = TypeVar('T')
 
     class Model(GenericModel, Generic[T]):
@@ -599,3 +598,168 @@ def test_multiple_specification():
         {'loc': ('a',), 'msg': 'none is not an allowed value', 'type': 'type_error.none.not_allowed'},
         {'loc': ('b',), 'msg': 'none is not an allowed value', 'type': 'type_error.none.not_allowed'},
     ]
+
+
+@skip_36
+def test_generic_model_pickle(create_module):
+    # Using create_module because pickle doesn't support
+    # objects with <locals> in their __qualname__  (e. g. defined in function)
+    @create_module
+    def module():
+        import pickle
+        from typing import Generic, TypeVar
+
+        from pydantic import BaseModel
+        from pydantic.generics import GenericModel
+
+        t = TypeVar('t')
+
+        class Model(BaseModel):
+            a: float
+            b: int = 10
+
+        class MyGeneric(GenericModel, Generic[t]):
+            value: t
+
+        original = MyGeneric[Model](value=Model(a='24'))
+        dumped = pickle.dumps(original)
+        loaded = pickle.loads(dumped)
+        assert loaded.value.a == original.value.a == 24
+        assert loaded.value.b == original.value.b == 10
+        assert loaded == original
+
+
+@skip_36
+def test_generic_model_from_function_pickle_fail(create_module):
+    @create_module
+    def module():
+        import pickle
+        from typing import Generic, TypeVar
+
+        import pytest
+
+        from pydantic import BaseModel
+        from pydantic.generics import GenericModel
+
+        t = TypeVar('t')
+
+        class Model(BaseModel):
+            a: float
+            b: int = 10
+
+        class MyGeneric(GenericModel, Generic[t]):
+            value: t
+
+        def get_generic(t):
+            return MyGeneric[t]
+
+        original = get_generic(Model)(value=Model(a='24'))
+        with pytest.raises(pickle.PicklingError):
+            pickle.dumps(original)
+
+
+@skip_36
+def test_generic_model_redefined_without_cache_fail(create_module):
+    @create_module
+    def module():
+        from typing import Generic, TypeVar
+
+        import pytest
+
+        from pydantic.generics import GenericModel, _generic_types_cache
+
+        t = TypeVar('t')
+
+        class MyGeneric(GenericModel, Generic[t]):
+            value: t
+
+        concrete = MyGeneric[t]
+        _generic_types_cache.clear()
+        with pytest.raises(
+            TypeError, match=r"'MyGeneric\[t\]' already defined above, please consider reusing it"
+        ) as exc_info:
+            MyGeneric[t]
+
+        cause = exc_info.value.__cause__
+        assert isinstance(cause, NameError), cause
+        expected_message = f"Name conflict: 'MyGeneric[t]' in {__name__!r} is already used by {concrete!r}"
+        assert cause.args[0] == expected_message, f'{cause.args[0]} != {expected_message}'
+
+
+def test_get_caller_module_name_from_function():
+    def get_current_module_name():
+        return get_caller_module_name()
+
+    assert get_current_module_name() == __name__
+
+
+def test_get_caller_module_name_from_module(create_module):
+    @create_module
+    def module():
+        from pydantic.generics import get_caller_module_name
+
+        def get_current_module_name():
+            return get_caller_module_name()
+
+        module_name = get_current_module_name()
+        assert module_name == __name__, f'{module_name} != {__name__}'
+
+        def get_current_module_name():
+            return get_caller_module_name()
+
+        assert get_current_module_name() == __name__
+
+
+def test_get_caller_module_name_not_found(mocker):
+    mocker.patch('inspect.getmodule', return_value=None)
+    assert get_caller_module_name() is None
+
+
+def test_is_call_from_module(create_module):
+    @create_module
+    def module():
+        from pydantic.generics import is_call_from_module
+
+        def function():
+            assert is_call_from_module()
+
+            another_function()
+
+        def another_function():
+            assert not is_call_from_module()
+            third_function()
+
+        def third_function():
+            assert not is_call_from_module()
+
+        function()
+
+
+def test_is_call_from_module_called_in_module(run_as_module):
+    @run_as_module
+    def module():
+        import pytest
+
+        from pydantic.generics import is_call_from_module
+
+        with pytest.raises(RuntimeError, match='This function must be used inside another function') as exc_info:
+            is_call_from_module()
+
+        e = exc_info.value
+        assert isinstance(e.__cause__, IndexError), e.__cause__
+        assert isinstance(e.__context__, IndexError), e.__context__
+
+
+def test_get_caller_module_called_from_module(run_as_module):
+    @run_as_module
+    def module():
+        import pytest
+
+        from pydantic.generics import get_caller_module_name
+
+        with pytest.raises(RuntimeError, match='This function must be used inside another function') as exc_info:
+            get_caller_module_name()
+
+        e = exc_info.value
+        assert isinstance(e.__cause__, IndexError), e.__cause__
+        assert isinstance(e.__context__, IndexError), e.__context__
diff --git a/tests/test_main.py b/tests/test_main.py
index b19b6f914..2bed15fe4 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -17,6 +17,7 @@ from pydantic import (
     constr,
     validator,
 )
+from pydantic.typing import Literal
 
 
 def test_success():
@@ -609,6 +610,34 @@ def test_enum_values():
     assert m.foo == 'foo'
 
 
+@pytest.mark.skipif(not Literal, reason='typing_extensions not installed')
+def test_literal_enum_values():
+    FooEnum = Enum('FooEnum', {'foo': 'foo_value', 'bar': 'bar_value'})
+
+    class Model(BaseModel):
+        baz: Literal[FooEnum.foo]
+        boo: str = 'hoo'
+
+        class Config:
+            use_enum_values = True
+
+    m = Model(baz=FooEnum.foo)
+    assert m.dict() == {'baz': 'foo_value', 'boo': 'hoo'}
+    assert m.baz.value == 'foo_value'
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(baz=FooEnum.bar)
+
+    assert exc_info.value.errors() == [
+        {
+            'loc': ('baz',),
+            'msg': "unexpected value; permitted: <FooEnum.foo: 'foo_value'>",
+            'type': 'value_error.const',
+            'ctx': {'given': FooEnum.bar, 'permitted': (FooEnum.foo,)},
+        },
+    ]
+
+
 def test_enum_raw():
     FooEnum = Enum('FooEnum', {'foo': 'foo', 'bar': 'bar'})
 
diff --git a/tests/test_schema.py b/tests/test_schema.py
index b08690ef2..9da7d14cc 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -80,11 +80,9 @@ def test_key():
         'title': 'ApplePie',
         'description': 'This is a test.',
     }
-    assert True not in ApplePie.__schema_cache__
-    assert False not in ApplePie.__schema_cache__
+    assert ApplePie.__schema_cache__.keys() == set()
     assert ApplePie.schema() == s
-    assert True in ApplePie.__schema_cache__
-    assert False not in ApplePie.__schema_cache__
+    assert ApplePie.__schema_cache__.keys() == {(True, '#/definitions/{model}')}
     assert ApplePie.schema() == s
 
 
@@ -110,6 +108,35 @@ def test_by_alias():
     assert list(ApplePie.schema(by_alias=False)['properties'].keys()) == ['a', 'b']
 
 
+def test_ref_template():
+    class KeyLimePie(BaseModel):
+        x: str = None
+
+    class ApplePie(BaseModel):
+        a: float = None
+        key_lime: KeyLimePie = None
+
+        class Config:
+            title = 'Apple Pie'
+
+    assert ApplePie.schema(ref_template='foobar/{model}.json') == {
+        'title': 'Apple Pie',
+        'type': 'object',
+        'properties': {'a': {'title': 'A', 'type': 'number'}, 'key_lime': {'$ref': 'foobar/KeyLimePie.json'}},
+        'definitions': {
+            'KeyLimePie': {
+                'title': 'KeyLimePie',
+                'type': 'object',
+                'properties': {'x': {'title': 'X', 'type': 'string'}},
+            },
+        },
+    }
+    assert ApplePie.schema()['properties']['key_lime'] == {'$ref': '#/definitions/KeyLimePie'}
+    json_schema = ApplePie.schema_json(ref_template='foobar/{model}.json')
+    assert 'foobar/KeyLimePie.json' in json_schema
+    assert '#/definitions/KeyLimePie' not in json_schema
+
+
 def test_by_alias_generator():
     class ApplePie(BaseModel):
         a: float
@@ -250,6 +277,103 @@ def test_enum_modify_schema():
     }
 
 
+def test_enum_schema_custom_field():
+    class FooBarEnum(str, Enum):
+        foo = 'foo'
+        bar = 'bar'
+
+    class Model(BaseModel):
+        pika: FooBarEnum = Field(alias='pikalias', title='Pikapika!', description='Pika is definitely the best!')
+        bulbi: FooBarEnum = Field('foo', alias='bulbialias', title='Bulbibulbi!', description='Bulbi is not...')
+        cara: FooBarEnum
+
+    assert Model.schema() == {
+        'definitions': {
+            'FooBarEnum': {
+                'description': 'An enumeration.',
+                'enum': ['foo', 'bar'],
+                'title': 'FooBarEnum',
+                'type': 'string',
+            }
+        },
+        'properties': {
+            'pikalias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Pika is definitely the best!',
+                'title': 'Pikapika!',
+            },
+            'bulbialias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Bulbi is not...',
+                'title': 'Bulbibulbi!',
+                'default': 'foo',
+            },
+            'cara': {'$ref': '#/definitions/FooBarEnum'},
+        },
+        'required': ['pikalias', 'cara'],
+        'title': 'Model',
+        'type': 'object',
+    }
+
+
+def test_enum_and_model_have_same_behaviour():
+    class Names(str, Enum):
+        rick = 'Rick'
+        morty = 'Morty'
+        summer = 'Summer'
+
+    class Pika(BaseModel):
+        a: str
+
+    class Foo(BaseModel):
+        enum: Names
+        titled_enum: Names = Field(
+            ...,
+            title='Title of enum',
+            description='Description of enum',
+        )
+        model: Pika
+        titled_model: Pika = Field(
+            ...,
+            title='Title of model',
+            description='Description of model',
+        )
+
+    assert Foo.schema() == {
+        'definitions': {
+            'Pika': {
+                'properties': {'a': {'title': 'A', 'type': 'string'}},
+                'required': ['a'],
+                'title': 'Pika',
+                'type': 'object',
+            },
+            'Names': {
+                'description': 'An enumeration.',
+                'enum': ['Rick', 'Morty', 'Summer'],
+                'title': 'Names',
+                'type': 'string',
+            },
+        },
+        'properties': {
+            'enum': {'$ref': '#/definitions/Names'},
+            'model': {'$ref': '#/definitions/Pika'},
+            'titled_enum': {
+                'allOf': [{'$ref': '#/definitions/Names'}],
+                'description': 'Description of enum',
+                'title': 'Title of enum',
+            },
+            'titled_model': {
+                'allOf': [{'$ref': '#/definitions/Pika'}],
+                'description': 'Description of model',
+                'title': 'Title of model',
+            },
+        },
+        'required': ['enum', 'titled_enum', 'model', 'titled_model'],
+        'title': 'Foo',
+        'type': 'object',
+    }
+
+
 def test_json_schema():
     class Model(BaseModel):
         a = b'foobar'
@@ -1087,7 +1211,17 @@ def test_schema_from_models():
     }
 
 
-def test_schema_with_ref_prefix():
+@pytest.mark.parametrize(
+    'ref_prefix,ref_template',
+    [
+        # OpenAPI style
+        ('#/components/schemas/', None),
+        (None, '#/components/schemas/{model}'),
+        # ref_prefix takes priority
+        ('#/components/schemas/', '#/{model}/schemas/'),
+    ],
+)
+def test_schema_with_refs(ref_prefix, ref_template):
     class Foo(BaseModel):
         a: str
 
@@ -1097,7 +1231,7 @@ def test_schema_with_ref_prefix():
     class Baz(BaseModel):
         c: Bar
 
-    model_schema = schema([Bar, Baz], ref_prefix='#/components/schemas/')  # OpenAPI style
+    model_schema = schema([Bar, Baz], ref_prefix=ref_prefix, ref_template=ref_template)
     assert model_schema == {
         'definitions': {
             'Baz': {
@@ -1122,6 +1256,55 @@ def test_schema_with_ref_prefix():
     }
 
 
+def test_schema_with_custom_ref_template():
+    class Foo(BaseModel):
+        a: str
+
+    class Bar(BaseModel):
+        b: Foo
+
+    class Baz(BaseModel):
+        c: Bar
+
+    model_schema = schema([Bar, Baz], ref_template='/schemas/{model}.json#/')
+    assert model_schema == {
+        'definitions': {
+            'Baz': {
+                'title': 'Baz',
+                'type': 'object',
+                'properties': {'c': {'$ref': '/schemas/Bar.json#/'}},
+                'required': ['c'],
+            },
+            'Bar': {
+                'title': 'Bar',
+                'type': 'object',
+                'properties': {'b': {'$ref': '/schemas/Foo.json#/'}},
+                'required': ['b'],
+            },
+            'Foo': {
+                'title': 'Foo',
+                'type': 'object',
+                'properties': {'a': {'title': 'A', 'type': 'string'}},
+                'required': ['a'],
+            },
+        }
+    }
+
+
+def test_schema_ref_template_key_error():
+    class Foo(BaseModel):
+        a: str
+
+    class Bar(BaseModel):
+        b: Foo
+
+    class Baz(BaseModel):
+        c: Bar
+
+    with pytest.raises(KeyError):
+        schema([Bar, Baz], ref_template='/schemas/{bad_name}.json#/')
+
+
 def test_schema_no_definitions():
     model_schema = schema([], title='Schema without definitions')
     assert model_schema == {'title': 'Schema without definitions'}
@@ -1803,7 +1986,6 @@ def test_model_process_schema_enum():
         bar = 'b'
 
     model_schema, _, _ = model_process_schema(SpamEnum, model_name_map={})
-    print(model_schema)
     assert model_schema == {'title': 'SpamEnum', 'description': 'An enumeration.', 'type': 'string', 'enum': ['f', 'b']}
 
 
diff --git a/tests/test_types.py b/tests/test_types.py
index 6752c6072..cde1cd0fe 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -726,6 +726,31 @@ def test_int_enum_successful_for_str_int():
     assert repr(m.tool) == '<ToolEnum.wrench: 2>'
 
 
+def test_enum_type():
+    """it should validate any Enum"""
+
+    class Model(BaseModel):
+        my_enum: Enum
+
+    Model(my_enum=FruitEnum.banana)
+    Model(my_enum=ToolEnum.wrench)
+    with pytest.raises(ValidationError):
+        Model(my_enum='banana')
+
+
+def test_int_enum_type():
+    """it should validate any IntEnum"""
+
+    class Model(BaseModel):
+        my_int_enum: IntEnum
+
+    Model(my_int_enum=ToolEnum.wrench)
+    with pytest.raises(ValidationError):
+        Model(my_int_enum=FruitEnum.banana)
+    with pytest.raises(ValidationError):
+        Model(my_int_enum=2)
+
+
 @pytest.mark.skipif(not email_validator, reason='email_validator not installed')
 def test_string_success():
     class MoreStringsModel(BaseModel):