Repository: samuelcolvin/pydantic
Pull request #1480 (2020-05-05 22:26:51)

schema ref_template
<!-- Thank you for your contribution! -->
<!-- Unless your change is trivial, please create an issue to discuss the change before creating a PR -->
<!-- See https://pydantic-docs.helpmanual.io/contributing/ for help on Contributing -->

## Change Summary

<!-- Please give a short summary of the changes. -->
Adds a `ref_template` option to `schema` generation that takes a [`string.Template`](https://docs.python.org/3/library/string.html#template-strings) and substitutes the `model_name` when generating `$ref`s to other schemas.

`my_model.schema(ref_template=string.Template("/foo/${model_name}.json#/bar"))`

### Edit:
Updated to just use a regular `.format()` template.
https://github.com/samuelcolvin/pydantic/issues/1479#issuecomment-630087550
```python
my_model.schema(ref_template="/foo/${model}.json#/bar")
```

## Related issue number

#1479

## Checklist

* [x] Unit tests for the changes exist
* [x] Tests pass on CI and coverage remains at 100%
* [ ] Documentation reflects the changes where applicable
* [x] `changes/<pull request or issue id>-<github username>.md` file added describing change
  (see [changes/README.md](https://github.com/samuelcolvin/pydantic/blob/master/changes/README.md) for details)
- [x] change to regular string.format() template

codecov[bot]: # [Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480?src=pr&el=h1) Report
> Merging [#1480](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480?src=pr&el=desc) into [master](https://codecov.io/gh/samuelcolvin/pydantic/commit/5067508eca6222b9315b1e38a30ddc975dee05e7&el=desc) will **decrease** coverage by `1.40%`.
> The diff coverage is `71.42%`.


```diff
@@             Coverage Diff             @@
##            master    #1480      +/-   ##
===========================================
- Coverage   100.00%   98.59%   -1.41%     
===========================================
  Files           21       21              
  Lines         3761     3766       +5     
  Branches       748      750       +2     
===========================================
- Hits          3761     3713      -48     
- Misses           0       41      +41     
- Partials         0       12      +12     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/schema.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvc2NoZW1hLnB5) | `99.45% <71.42%> (-0.55%)` | :arrow_down: |
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `79.41% <0.00%> (-20.59%)` | :arrow_down: |
| [pydantic/decorator.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvZGVjb3JhdG9yLnB5) | `86.30% <0.00%> (-13.70%)` | :arrow_down: |
| [pydantic/version.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/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/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvZW52X3NldHRpbmdzLnB5) | `97.33% <0.00%> (-2.67%)` | :arrow_down: |
| [pydantic/utils.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvdXRpbHMucHk=) | `99.02% <0.00%> (-0.98%)` | :arrow_down: |
| [pydantic/networks.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvbmV0d29ya3MucHk=) | `99.21% <0.00%> (-0.79%)` | :arrow_down: |
| [pydantic/validators.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480/diff?src=pr&el=tree#diff-cHlkYW50aWMvdmFsaWRhdG9ycy5weQ==) | `99.42% <0.00%> (-0.58%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480?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/1480?src=pr&el=footer). Last update [5067508...de3e038](https://codecov.io/gh/samuelcolvin/pydantic/pull/1480?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

samuelcolvin: sorry for the slow reply, as I mentioned on the issue #1479, I think a normal `str` should be used not `Template`.

It's not necessary, and `Template` is very rarely used so will confuse many people.
Kilo59: > sorry for the slow reply, as I mentioned on the issue #1479, I think a normal `str` should be used not `Template`.
> 
> It's not necessary, and `Template` is very rarely used so will confuse many people.

Makes sense and thanks for the reminder. I'll try to update this over the weekend. 
Kilo59: Getting a failure on `test_constrained_set_default` that I don't understand.
https://github.com/samuelcolvin/pydantic/runs/837388454
```python
>           raise validation_error
E           pydantic.error_wrappers.ValidationError: 1 validation error for ConSetModelMax
E           v
E             value is not a valid set (type=type_error.set)

pydantic/main.py:346: ValidationError
```
samuelcolvin: See #1682
Kilo59: @samuelcolvin should I also update the schema-customization section docs?
If so should I say anything about `ref_prefix` being deprecated in the future etc?
https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization
samuelcolvin: This is looking great, but you still need to:
* [ ] fix coverage (there are two cases) which are currently not covered
* [ ] add something to docs.
Kilo59: > This is looking great, but you still need to:
> 
> * [ ]  fix coverage (there are two cases) which are currently not covered

What are the cases that are not covered?
samuelcolvin: look at the codecov report above.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 97e5776b0..67d4ad8d9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,6 +9,34 @@ 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
@@ -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/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/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/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/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 377869a54..1ce891d0b 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,6 @@
 ansi2html==1.5.2
+flake8==3.8.4
+flake8-quotes==3.2.0
 mkdocs==1.1.2
 markdown==3.3.1
 mkdocs-exclude==1.0.2
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 24015db6d..316c6a2cb 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -257,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:
@@ -274,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/main.py b/pydantic/main.py
index 8fc85e75a..55260d03e 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -31,7 +31,7 @@ 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, get_origin, is_classvar, resolve_annotations, update_field_forward_refs
 from .utils import (
@@ -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:
@@ -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':
@@ -718,7 +715,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__:
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 533620f74..db470759b 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -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,20 +140,22 @@ 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
@@ -179,6 +191,7 @@ def field_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]]:
     """
@@ -192,10 +205,12 @@ 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
     s, schema_overrides = get_field_info_schema(field)
 
     validation_schema = get_field_schema_validations(field)
@@ -209,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
@@ -380,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,
@@ -393,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)
@@ -409,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)
@@ -425,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)
@@ -441,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)
@@ -460,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]]:
     """
@@ -471,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)
@@ -484,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
@@ -503,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]]:
@@ -512,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] = {}
@@ -520,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)
@@ -578,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,
@@ -588,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()]
@@ -599,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:
@@ -610,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)
@@ -669,8 +713,11 @@ def add_field_type_to_schema(field_type: Any, schema: Dict[str, Any]) -> None:
             break
 
 
-def get_schema_ref(ref_name: str, schema_overrides: bool) -> Dict[str, Any]:
-    schema_ref = {'$ref': ref_name}
+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
 
 
@@ -679,6 +726,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
     *,
     by_alias: bool,
     model_name_map: Dict[TypeModelOrEnum, str],
+    ref_template: str,
     schema_overrides: bool = False,
     ref_prefix: Optional[str] = None,
     known_models: TypeModelSet,
@@ -690,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:
@@ -700,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:
@@ -718,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]
@@ -727,7 +776,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
     if lenient_issubclass(field_type, Enum):
         enum_name = normalize_name(field_type.__name__)
         f_schema, schema_overrides = get_field_info_schema(field)
-        f_schema.update(get_schema_ref(ref_prefix + enum_name, schema_overrides))
+        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)
@@ -751,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)
@@ -758,7 +808,7 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
             nested_models.update(sub_nested_models)
         else:
             nested_models.add(model_name)
-        schema_ref = get_schema_ref(ref_prefix + model_name, schema_overrides)
+        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}')
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 5b55ae7ec..d204f8fc0 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -30,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
 
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 18ca8393b..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.py39
-
 [coverage:run]
 source = pydantic
 branch = True
diff --git a/setup.py b/setup.py
index 4f5f6b846..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


Test patch:
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.txt b/tests/requirements-linting.txt
similarity index 58%
rename from tests/requirements.txt
rename to tests/requirements-linting.txt
index 8fee4e032..336553d16 100644
--- a/tests/requirements.txt
+++ b/tests/requirements-linting.txt
@@ -1,13 +1,8 @@
 black==20.8b1
-coverage==5.3
 flake8==3.8.4
 flake8-quotes==3.2.0
 isort==5.6.4
 mypy==0.790
 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/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/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_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_schema.py b/tests/test_schema.py
index ef5d2b0eb..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
@@ -1184,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
 
@@ -1194,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': {
@@ -1219,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'}
@@ -1900,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']}