Repository: samuelcolvin/pydantic
Pull request #2147 (2020-11-26 18:28:09)

Support Annotated type hints and extracting Field from Annotated
## Change Summary

As discussed elsewhere ([1](https://github.com/samuelcolvin/pydantic/issues/1984#issuecomment-720114619), [2](https://github.com/samuelcolvin/pydantic/issues/2129)), Pydantic doesn't support `typing.Annotated`/`typing_extensions.Annotated`. When using an `Annotated` field, Pydantic raises:

```
TypeError: Fields of type "<class 'typing.Annotated'>" are not supported.
```

This PR adds support for `Annotated` by simply unwrapping the root type in `ModelField`. The full `Annotated` information is still visible with `get_type_hints(x, include_extras=True)` on the `BaseModel` subclasses.

I'm not sure if this needs doc updates, perhaps just a small note saying "Annotated type hints are supported"?

Separately, I'm working on a prototype for https://github.com/samuelcolvin/pydantic/issues/2129 in https://github.com/JacobHayes/pydantic-annotated which expands on this functionality. I have a small check (based on the presence of `pydantic.typing.Annotated`) in there to do the unwrapping until this or similar is merged.

## Related issue number

- https://github.com/samuelcolvin/pydantic/issues/2129

## Checklist

* [x] Unit tests for the changes exist
* [ ] 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/2147?src=pr&el=h1) Report
> Merging [#2147](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147?src=pr&el=desc) (dc9faa3) into [master](https://codecov.io/gh/samuelcolvin/pydantic/commit/31bc2435d7968fdf8d1f0c5c67f0f851c1bef54e?el=desc) (31bc243) will **decrease** coverage by `1.04%`.
> The diff coverage is `30.00%`.


```diff
@@             Coverage Diff             @@
##            master    #2147      +/-   ##
===========================================
- Coverage   100.00%   98.95%   -1.05%     
===========================================
  Files           21       21              
  Lines         4093     4102       +9     
  Branches       823      824       +1     
===========================================
- Hits          4093     4059      -34     
- Misses           0       35      +35     
- Partials         0        8       +8     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/fields.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/diff?src=pr&el=tree#diff-cHlkYW50aWMvZmllbGRzLnB5) | `99.07% <0.00%> (-0.93%)` | :arrow_down: |
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `75.96% <50.00%> (-24.04%)` | :arrow_down: |
| [pydantic/version.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/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/2147/diff?src=pr&el=tree#diff-cHlkYW50aWMvZW52X3NldHRpbmdzLnB5) | `97.93% <0.00%> (-2.07%)` | :arrow_down: |
| [pydantic/networks.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/diff?src=pr&el=tree#diff-cHlkYW50aWMvbmV0d29ya3MucHk=) | `99.22% <0.00%> (-0.78%)` | :arrow_down: |
| [pydantic/utils.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/diff?src=pr&el=tree#diff-cHlkYW50aWMvdXRpbHMucHk=) | `99.29% <0.00%> (-0.71%)` | :arrow_down: |
| [pydantic/decorator.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147/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/2147?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/2147?src=pr&el=footer). Last update [31bc243...dc9faa3](https://codecov.io/gh/samuelcolvin/pydantic/pull/2147?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

JacobHayes: This appears to be _mostly_ working (and is for 3.{6,7}) except when using a constrained type (`gt`, et al):
```python
On field "x" the following field constraints are set but not enforced
```

Looks like I'll need a little more digging into the constraint logic to apply a `get_origin` or similar.
samuelcolvin: I think this looks like a great start, but I think before we merge we need:
* `Annotated[int, Field(alias='foobar')]` to work - I guess we should raise an error if you set the default value by via the positional argument to `Field`
* decent documentation on how to use `Annotated`, shouldn;t be that hard
JacobHayes: > I guess we should raise an error if you set the default value by via the positional argument to Field

Can you expand on this? Is this because "ideally" `Field` wouldn't take the default _at all_, it only has to now because it replaces the rhs?
JacobHayes: @samuelcolvin any chance we could make `typing_extensions` a required dependency? Would certainly clean up some code/cases for Annotated, Literal, and maybe some other stuff in `typing.py`
JacobHayes: Looks like we need to either bump the docs builder to py3.9, install typing_extensions, or I can just inline the example (so it's not run). Any preferences?

I also added a few failing tests around defaults - I need to add:
- error if the `Field` in `Annotated` has a default set
- use the rhs default value when `Field` is in `Annotated`
JacobHayes: I don't think we can (cleanly) "error if the Field in Annotated has a default set" since we also need to support `default_factory`, unless:
- we use `default_factory` from lhs `Annotated[..., Field(...)]` but `default` from rhs value (more complexity/error cases)
- or, we inspect `isinstance(rhs, Callable)` to infer `default_factory` (larger/breaking change that should be separate _if_ desirable, [potentially a v2 change](https://github.com/samuelcolvin/pydantic/issues/866#issuecomment-583350898))

For now, I'll allow the defaults to be set in the lhs, but let me know if you prefer one of the two above.
JacobHayes: @samuelcolvin I think this is ready for another look!

There may be a bit of a usage disconnect between `default`/`default_factory` that might be improved with [this potential v2 change](https://github.com/samuelcolvin/pydantic/issues/866#issuecomment-583350898).
JacobHayes: Good on my end - open discussions:
- [`_get_field_info ` naming](https://github.com/samuelcolvin/pydantic/pull/2147#discussion_r551705244)
- [custom `get_args`/`get_origin` strategy](https://github.com/samuelcolvin/pydantic/pull/2147#discussion_r556982407)
thomascobb: I've added a test for my use case in https://github.com/thomascobb/pydantic/blob/support-annotated-fields/tests/test_decorator.py:

```python
def test_annotated_field_reuse():
    AOne = Annotated[int, Field(description='A long description of `one` we don\'t want to repeat')]

    class Model(BaseModel):
        one: AOne
        two: Annotated[int, Field(description='An optional integer')] = 3

        @classmethod
        @validate_arguments
        def alternative(
            cls,
            one: AOne,
            have_two: Annotated[bool, Field(description='Include two?')],
        ) -> 'Model':
            return cls(one=one, two=2 if have_two else 0)

    assert Model(one=1).dict() == dict(one=1, two=3)
    assert Model.alternative(one=1, have_two=False) == dict(one=1, two=0)
```
This is failing for a few reasons. First is in the type hint evaluations:
```
tests/test_decorator.py:134: in Model
    def alternative(
pydantic/decorator.py:61: in validate_arguments
    return validate(func)
pydantic/decorator.py:48: in validate
    vd = ValidatedFunction(_func, config)
pydantic/decorator.py:89: in __init__
    type_hints = get_type_hints(function)
/usr/lib/python3.8/typing.py:1264: in get_type_hints
    value = _eval_type(value, globalns, localns)
/usr/lib/python3.8/typing.py:270: in _eval_type
    return t._evaluate(globalns, localns)
/usr/lib/python3.8/typing.py:518: in _evaluate
    eval(self.__forward_code__, globalns, localns),
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

>   ???
E   NameError: name 'Model' is not defined

<string>:1: NameError
```
If I remove the `-> 'Model'` in the return type of `alternative` then I get:
```
tests/test_decorator.py:128: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
pydantic/main.py:265: in __new__
    fields[ann_name] = inferred = ModelField.infer(
pydantic/fields.py:346: in infer
    field_info, value = cls._get_field_info(name, annotation, value, config)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

field_name = 'one'
annotation = typing_extensions.Annotated[int, FieldInfo(default=Ellipsis, description="A long description of `one` we don't want to repeat", extra={})]
value = PydanticUndefined, config = <class 'pydantic.main.Config'>

    @staticmethod
    def _get_field_info(
        field_name: str, annotation: Any, value: Any, config: Type['BaseConfig']
    ) -> Tuple[FieldInfo, Any]:
        """
        Get a FieldInfo from a root typing.Annotated annotation, value, or config default.
    
        The FieldInfo may be set in typing.Annotated or the value, but not both. If neither contain
        a FieldInfo, a new one will be created using the config.
    
        :param field_name: name of the field for use in error messages
        :param annotation: a type hint such as `str` or `Annotated[str, Field(..., min_length=5)]`
        :param value: the field's assigned value
        :param config: the model's config object
        :return: the FieldInfo contained in the `annotation`, the value, or a new one from the config.
        """
        field_info_from_config = config.get_field_info(field_name)
    
        field_info = None
        if get_origin(annotation) is Annotated:
            field_infos = [arg for arg in get_args(annotation)[1:] if isinstance(arg, FieldInfo)]
            if len(field_infos) > 1:
                raise ValueError(f'cannot specify multiple `Annotated` `Field`s for {field_name!r}')
            field_info = next(iter(field_infos), None)
            if field_info is not None:
                if field_info.default is not Undefined:
>                   raise ValueError(f'`Field` default cannot be set in `Annotated` for {field_name!r}')
E                   ValueError: `Field` default cannot be set in `Annotated` for 'one'

pydantic/fields.py:320: ValueError
```
Finally, if I run `make lint` I get:
```
tests/test_decorator.py:126:45: F722 syntax error in forward annotation "A long description of `one` we don't want to repeat"
tests/test_decorator.py:126:45: Q003 Change outer quotes to avoid escaping inner quotes
tests/test_decorator.py:130:47: F722 syntax error in forward annotation 'An optional integer'
tests/test_decorator.py:137:57: F722 syntax error in forward annotation 'Include two?'
```
Any ideas?


PrettyWood: @thomascobb The error with `NameError: name 'Model' is not defined` comes from `pyflakes`. I opened a [PR](https://github.com/PyCQA/pyflakes/pull/600) to fix it but there seems to be new issues with it 🙄 
The Q003 error is expected to change `'...we don\'t...'` into `"...we don't..."`

JacobHayes: @PrettyWood 
> Unfortunately this PR can't be merged because other issues have been spotted and I still haven't taken the time to dig into it

Do you mind linking to or describing some of the other issues (aside from @thomascobb's) in case I or others have a chance to check them out? If this gets too messy, I'm also happy to split this PR up into 2 parts - the first PR to just _not_ break on **unrelated** `Annotated` uses (the first commit) and a second PR to support `Field` in `Annotated` (much messier 😁).

@thomascobb
I just pushed a commit that should fix that `Field` reuse issue - thanks for the find! I admittedly don't grok the distinction between `Ellipsis` and `Undefined` (anything aside from `print`ing nicety?), but the `FieldInfo` mutation definitely adds a little complexity.

>E   NameError: name 'Model' is not defined

This part is unrelated to this PR/doesn't work on `master` unfortunately either.
<details><summary>broken minimum test_model_forward_ref</summary>

```python
def test_model_forward_ref():
    from pydantic.decorator import validate_arguments

    class Model(BaseModel):
        one: int

        @classmethod
        @validate_arguments
        def alternative(cls, one) -> 'Model':  # still fails
            return cls(one=one)
```

</details>

I think it's due to the `alternative` method being created and `validate_arguments` called before `Model` is created. Perhaps there's a way to keep `validate_arguments` lazy here and do something in `Model.update_forward_refs()`, but in the short term you might have luck defining the function outside the class body.
<details><summary>working test_model_forward_ref</summary>

```python
def test_model_forward_ref():
    from pydantic.decorator import validate_arguments

    class Model(BaseModel):
        one: int

    @classmethod
    @validate_arguments
    def alternative(cls, one) -> Model:
        return cls(one=one)

    Model.alternative = alternative
```

</details>
PrettyWood: The PR that can't be merged yet is the fix I made for `pyflakes`.
thomascobb: > I just pushed a commit that should fix that `Field` reuse issue - thanks for the find! I admittedly don't grok the distinction between `Ellipsis` and `Undefined` (anything aside from `print`ing nicety?), but the `FieldInfo` mutation definitely adds a little complexity.

Thanks, that fixes it

> This part is unrelated to this PR/doesn't work on `master` unfortunately either.

Good point. The following works though, and it probably more correct...

```python
from typing import TypeVar

def test_annotated_field_reuse():
    T = TypeVar("T")
    AOne = Annotated[int, Field(description='A long description of `one` we don\'t want to repeat')]

    class Model(BaseModel):
        one: AOne
        two: Annotated[int, Field(description='An optional integer')] = 3

        @classmethod
        @validate_arguments
        def alternative(
            cls: T,
            one: AOne,
            have_two: Annotated[bool, Field(description='Include two?')],
        ) -> T:
            return cls(one=one, two=2 if have_two else 0)

    assert Model(one=1).dict() == dict(one=1, two=3)
    assert Model.alternative(one=1, have_two=False) == dict(one=1, two=0)
```
samuelcolvin: I'm a bit unclear on this.

Is there anything (except conflicts which are trivial) which is blocking this? I would be keen to get it merged asap to include in v1.8 if possible.
JacobHayes: No blockers I'm aware of, just another round of reviews. I'll push a conflict fix for reqs in a min.

Patch:
diff --git a/changes/2064-art049.md b/changes/2064-art049.md
new file mode 100644
index 000000000..426c3f379
--- /dev/null
+++ b/changes/2064-art049.md
@@ -0,0 +1,1 @@
+Add merged `json_encoders` inheritance
\ No newline at end of file
diff --git a/changes/2147-JacobHayes.md b/changes/2147-JacobHayes.md
new file mode 100644
index 000000000..322da5ffb
--- /dev/null
+++ b/changes/2147-JacobHayes.md
@@ -0,0 +1,1 @@
+Support `typing.Annotated` hints on model fields. A `Field` may now be set in the type hint with `Annotated[..., Field(...)`; all other annotations are ignored but still visible with `get_type_hints(..., include_extras=True)`.
diff --git a/changes/2228-samuelcolvin.md b/changes/2228-samuelcolvin.md
new file mode 100644
index 000000000..05d4606f0
--- /dev/null
+++ b/changes/2228-samuelcolvin.md
@@ -0,0 +1,1 @@
+ensure cythonized functions are left untouched when creating models, based on #1944 by @kollmats
diff --git a/docs/examples/exporting_models_json_encoders_merge.py b/docs/examples/exporting_models_json_encoders_merge.py
new file mode 100644
index 000000000..b1562cdd0
--- /dev/null
+++ b/docs/examples/exporting_models_json_encoders_merge.py
@@ -0,0 +1,24 @@
+from datetime import datetime, timedelta
+from pydantic import BaseModel
+from pydantic.json import timedelta_isoformat
+
+
+class BaseClassWithEncoders(BaseModel):
+    dt: datetime
+    diff: timedelta
+
+    class Config:
+        json_encoders = {
+            datetime: lambda v: v.timestamp()
+        }
+
+
+class ChildClassWithEncoders(BaseClassWithEncoders):
+    class Config:
+        json_encoders = {
+            timedelta: timedelta_isoformat
+        }
+
+
+m = ChildClassWithEncoders(dt=datetime(2032, 6, 1), diff=timedelta(hours=100))
+print(m.json())
diff --git a/docs/examples/schema_annotated.py b/docs/examples/schema_annotated.py
new file mode 100644
index 000000000..ab29ad38a
--- /dev/null
+++ b/docs/examples/schema_annotated.py
@@ -0,0 +1,13 @@
+from uuid import uuid4
+
+try:
+    from typing import Annotated
+except ImportError:
+    from typing_extensions import Annotated
+
+from pydantic import BaseModel, Field
+
+
+class Foo(BaseModel):
+    id: Annotated[str, Field(default_factory=lambda: uuid4().hex)]
+    name: Annotated[str, Field(max_length=256)] = 'Bar'
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 87e939b08..a12fff609 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -7,5 +7,6 @@ mkdocs-exclude==1.0.2
 mkdocs-material==6.2.8
 markdown-include==0.6.0
 sqlalchemy
+typing-extensions==3.7.4
 orjson
 ujson
diff --git a/docs/usage/exporting_models.md b/docs/usage/exporting_models.md
index 26a6b0fa4..15cbd206b 100644
--- a/docs/usage/exporting_models.md
+++ b/docs/usage/exporting_models.md
@@ -99,11 +99,19 @@ _(This script is complete, it should run "as is")_
 By default, `timedelta` is encoded as a simple float of total seconds. The `timedelta_isoformat` is provided
 as an optional alternative which implements ISO 8601 time diff encoding.
 
+The `json_encoders` are also merged during the models inheritance with the child
+encoders taking precedence over the parent one.
+
+```py
+{!.tmp_examples/exporting_models_json_encoders_merge.py!}
+```
+_(This script is complete, it should run "as is")_
+
 ### Serialising subclasses
 
 !!! note
     New in version **v1.5**.
-    
+
     Subclasses of common types were not automatically serialised to JSON before **v1.5**.
 
 Subclasses of common types are automatically encoded like their super-classes:
diff --git a/docs/usage/schema.md b/docs/usage/schema.md
index f2f889df8..7ec256c90 100644
--- a/docs/usage/schema.md
+++ b/docs/usage/schema.md
@@ -106,6 +106,21 @@ to `Field()` with the raw schema attribute name:
 ```
 _(This script is complete, it should run "as is")_
 
+### typing.Annotated Fields
+
+Rather than assigning a `Field` value, it can be specified in the type hint with `typing.Annotated`:
+
+```py
+{!.tmp_examples/schema_annotated.py!}
+```
+_(This script is complete, it should run "as is")_
+
+`Field` can only be supplied once per field - an error will be raised if used in `Annotated` and as the assigned value.
+Defaults can be set outside `Annotated` as the assigned value or with `Field.default_factory` inside `Annotated` - the
+`Field.default` argument is not supported inside `Annotated`.
+
+For versions of Python prior to 3.9, `typing_extensions.Annotated` can be used.
+
 ## Modifying schema in custom fields
 
 Custom field types can customise the schema generated for them using the `__modify_schema__` class method;
diff --git a/docs/usage/types.md b/docs/usage/types.md
index 39aeb92a8..929188fff 100644
--- a/docs/usage/types.md
+++ b/docs/usage/types.md
@@ -72,6 +72,11 @@ with custom properties and validation.
 `typing.Any`
 : allows any value include `None`, thus an `Any` field is optional
 
+`typing.Annotated`
+: allows wrapping another type with arbitrary metadata, as per [PEP-593](https://www.python.org/dev/peps/pep-0593/). The
+  `Annotated` hint may contain a single call to the [`Field` function](schema.md#typingannotated-fields), but otherwise
+  the additional metadata is ignored and the root type is used.
+
 `typing.TypeVar`
 : constrains the values allowed based on `constraints` or `bound`, see [TypeVar](#typevar)
 
diff --git a/pydantic/fields.py b/pydantic/fields.py
index 578f2a882..f75e16f1f 100644
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -29,6 +29,7 @@ from .errors import ConfigError, NoneIsNotAllowedError
 from .types import Json, JsonWrapper
 from .typing import (
     NONE_TYPES,
+    Annotated,
     Callable,
     ForwardRef,
     NoArgAnyCallable,
@@ -120,6 +121,10 @@ class FieldInfo(Representation):
         self.regex = kwargs.pop('regex', None)
         self.extra = kwargs
 
+    def _validate(self) -> None:
+        if self.default not in (Undefined, Ellipsis) and self.default_factory is not None:
+            raise ValueError('cannot specify both default and default_factory')
+
 
 def Field(
     default: Any = Undefined,
@@ -171,10 +176,7 @@ def Field(
       pattern string. The schema will have a ``pattern`` validation keyword
     :param **extra: any additional keyword arguments will be added as is to the schema
     """
-    if default is not Undefined and default_factory is not None:
-        raise ValueError('cannot specify both default and default_factory')
-
-    return FieldInfo(
+    field_info = FieldInfo(
         default,
         default_factory=default_factory,
         alias=alias,
@@ -193,6 +195,8 @@ def Field(
         regex=regex,
         **extra,
     )
+    field_info._validate()
+    return field_info
 
 
 def Schema(default: Any, **kwargs: Any) -> Any:
@@ -288,6 +292,46 @@ class ModelField(Representation):
     def get_default(self) -> Any:
         return smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()
 
+    @staticmethod
+    def _get_field_info(
+        field_name: str, annotation: Any, value: Any, config: Type['BaseConfig']
+    ) -> Tuple[FieldInfo, Any]:
+        """
+        Get a FieldInfo from a root typing.Annotated annotation, value, or config default.
+
+        The FieldInfo may be set in typing.Annotated or the value, but not both. If neither contain
+        a FieldInfo, a new one will be created using the config.
+
+        :param field_name: name of the field for use in error messages
+        :param annotation: a type hint such as `str` or `Annotated[str, Field(..., min_length=5)]`
+        :param value: the field's assigned value
+        :param config: the model's config object
+        :return: the FieldInfo contained in the `annotation`, the value, or a new one from the config.
+        """
+        field_info_from_config = config.get_field_info(field_name)
+
+        field_info = None
+        if get_origin(annotation) is Annotated:
+            field_infos = [arg for arg in get_args(annotation)[1:] if isinstance(arg, FieldInfo)]
+            if len(field_infos) > 1:
+                raise ValueError(f'cannot specify multiple `Annotated` `Field`s for {field_name!r}')
+            field_info = next(iter(field_infos), None)
+            if field_info is not None:
+                if field_info.default not in (Undefined, Ellipsis):
+                    raise ValueError(f'`Field` default cannot be set in `Annotated` for {field_name!r}')
+                if value not in (Undefined, Ellipsis):
+                    field_info.default = value
+        if isinstance(value, FieldInfo):
+            if field_info is not None:
+                raise ValueError(f'cannot specify `Annotated` and value `Field`s together for {field_name!r}')
+            field_info = value
+        if field_info is None:
+            field_info = FieldInfo(value, **field_info_from_config)
+        field_info.alias = field_info.alias or field_info_from_config.get('alias')
+        value = None if field_info.default_factory is not None else field_info.default
+        field_info._validate()
+        return field_info, value
+
     @classmethod
     def infer(
         cls,
@@ -298,21 +342,15 @@ class ModelField(Representation):
         class_validators: Optional[Dict[str, Validator]],
         config: Type['BaseConfig'],
     ) -> 'ModelField':
-        field_info_from_config = config.get_field_info(name)
         from .schema import get_annotation_from_field_info
 
-        if isinstance(value, FieldInfo):
-            field_info = value
-            value = None if field_info.default_factory is not None else field_info.default
-        else:
-            field_info = FieldInfo(value, **field_info_from_config)
+        field_info, value = cls._get_field_info(name, annotation, value, config)
         required: 'BoolUndefined' = Undefined
         if value is Required:
             required = True
             value = None
         elif value is not Undefined:
             required = False
-        field_info.alias = field_info.alias or field_info_from_config.get('alias')
         annotation = get_annotation_from_field_info(annotation, field_info, name)
         return cls(
             name=name,
@@ -427,6 +465,10 @@ class ModelField(Representation):
             if isinstance(self.type_, type) and isinstance(None, self.type_):
                 self.allow_none = True
             return
+        if origin is Annotated:
+            self.type_ = get_args(self.type_)[0]
+            self._type_analysis()
+            return
         if origin is Callable:
             return
         if origin is Union:
diff --git a/pydantic/main.py b/pydantic/main.py
index c00cd1f7a..c2f8cb37f 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -169,13 +169,19 @@ class BaseConfig:
 
 
 def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType') -> 'ConfigType':
+    namespace = {}
     if not self_config:
         base_classes = (parent_config,)
     elif self_config == parent_config:
         base_classes = (self_config,)
     else:
         base_classes = self_config, parent_config  # type: ignore
-    return type('Config', base_classes, {})
+        namespace['json_encoders'] = {
+            **getattr(parent_config, 'json_encoders', {}),
+            **getattr(self_config, 'json_encoders', {}),
+        }
+
+    return type('Config', base_classes, namespace)
 
 
 EXTRA_LINK = 'https://pydantic-docs.helpmanual.io/usage/model_config/'
@@ -259,6 +265,11 @@ class ModelMetaclass(ABCMeta):
 
         prepare_config(config, name)
 
+        untouched_types = ANNOTATED_FIELD_UNTOUCHED_TYPES
+
+        def is_untouched(v: Any) -> bool:
+            return isinstance(v, untouched_types) or v.__class__.__name__ == 'cython_function_or_method'
+
         if (namespace.get('__module__'), namespace.get('__qualname__')) != ('pydantic.main', 'BaseModel'):
             annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
             # annotation only fields need to come first in fields
@@ -270,7 +281,7 @@ class ModelMetaclass(ABCMeta):
                     value = namespace.get(ann_name, Undefined)
                     allowed_types = get_args(ann_type) if get_origin(ann_type) is Union else (ann_type,)
                     if (
-                        isinstance(value, ANNOTATED_FIELD_UNTOUCHED_TYPES)
+                        is_untouched(value)
                         and ann_type != PyObject
                         and not any(
                             lenient_issubclass(get_origin(allowed_type), Type) for allowed_type in allowed_types
@@ -289,7 +300,7 @@ class ModelMetaclass(ABCMeta):
 
             untouched_types = UNTOUCHED_TYPES + config.keep_untouched
             for var_name, value in namespace.items():
-                can_be_changed = var_name not in class_vars and not isinstance(value, untouched_types)
+                can_be_changed = var_name not in class_vars and not is_untouched(value)
                 if isinstance(value, ModelPrivateAttr):
                     if not is_valid_private_name(var_name):
                         raise NameError(
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 6d80ea302..e1e14912b 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -59,6 +59,7 @@ from .types import (
 )
 from .typing import (
     NONE_TYPES,
+    Annotated,
     ForwardRef,
     Literal,
     get_args,
@@ -917,6 +918,8 @@ def get_annotation_from_field_info(annotation: Any, field_info: FieldInfo, field
                 # forward refs cause infinite recursion below
                 return type_
 
+            if origin is Annotated:
+                return go(args[0])
             if origin is Union:
                 return Union[tuple(go(a) for a in args)]  # type: ignore
 
diff --git a/pydantic/typing.py b/pydantic/typing.py
index 91cdf4e6b..cf4f25e58 100644
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -80,6 +80,38 @@ else:
     AnyCallable = TypingCallable[..., Any]
     NoArgAnyCallable = TypingCallable[[], Any]
 
+
+if sys.version_info >= (3, 9):
+    from typing import Annotated
+else:
+    if TYPE_CHECKING:
+        from typing_extensions import Annotated
+    else:  # due to different mypy warnings raised during CI for python 3.7 and 3.8
+        try:
+            from typing_extensions import Annotated
+        except ImportError:
+            # Create mock Annotated values distinct from `None`, which is a valid `get_origin`
+            # return value.
+            class _FalseMeta(type):
+                # Allow short circuiting with "Annotated[...] if Annotated else None".
+                def __bool__(cls):
+                    return False
+
+                # Give a nice suggestion for unguarded use
+                def __getitem__(cls, key):
+                    raise RuntimeError(
+                        'Annotated is not supported in this python version, please `pip install typing-extensions`.'
+                    )
+
+            class Annotated(metaclass=_FalseMeta):
+                pass
+
+
+# Annotated[...] is implemented by returning an instance of one of these classes, depending on
+# python/typing_extensions version.
+AnnotatedTypeNames = ('AnnotatedMeta', '_AnnotatedAlias')
+
+
 if sys.version_info < (3, 8):  # noqa: C901
     if TYPE_CHECKING:
         from typing_extensions import Literal
@@ -100,6 +132,8 @@ if sys.version_info < (3, 8):  # noqa: C901
             python 3.6).
 
             """
+            if Annotated and type(t).__name__ in AnnotatedTypeNames:
+                return t.__args__ + t.__metadata__
             return getattr(t, '__args__', ())
 
     else:
@@ -111,6 +145,8 @@ if sys.version_info < (3, 8):  # noqa: C901
             Mostly compatible with the python 3.8 `typing` module version
             and able to handle almost all use cases.
             """
+            if Annotated and type(t).__name__ in AnnotatedTypeNames:
+                return t.__args__ + t.__metadata__
             if isinstance(t, _GenericAlias):
                 res = t.__args__
                 if t.__origin__ is Callable and res and res[0] is not Ellipsis:
@@ -119,6 +155,8 @@ if sys.version_info < (3, 8):  # noqa: C901
             return getattr(t, '__args__', ())
 
     def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
+        if Annotated and type(t).__name__ in AnnotatedTypeNames:
+            return cast(Type[Any], Annotated)  # mypy complains about _SpecialForm in py3.6
         return getattr(t, '__origin__', None)
 
 
@@ -132,6 +170,8 @@ else:
         It should be useless once https://github.com/cython/cython/issues/3537 is
         solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged.
         """
+        if Annotated and type(tp).__name__ in AnnotatedTypeNames:
+            return cast(Type[Any], Annotated)  # mypy complains about _SpecialForm
         return typing_get_origin(tp) or getattr(tp, '__origin__', None)
 
     def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
@@ -156,6 +196,8 @@ else:
             get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
             get_args(Callable[[], T][int]) == ([], int)
         """
+        if Annotated and type(tp).__name__ in AnnotatedTypeNames:
+            return tp.__args__ + tp.__metadata__
         # the fallback is needed for the same reasons as `get_origin` (see above)
         return typing_get_args(tp) or getattr(tp, '__args__', ()) or generic_get_args(tp)
 
@@ -178,6 +220,7 @@ if TYPE_CHECKING:
 __all__ = (
     'ForwardRef',
     'Callable',
+    'Annotated',
     'AnyCallable',
     'NoArgAnyCallable',
     'NoneType',
diff --git a/setup.cfg b/setup.cfg
index 0154d92e9..93865e164 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,6 +4,7 @@ addopts = -p no:hypothesispytest
 filterwarnings =
     error
     ignore::DeprecationWarning:distutils
+    ignore::DeprecationWarning:Cython
 
 [flake8]
 max-line-length = 120
diff --git a/setup.py b/setup.py
index bbed2bec9..e6b72c446 100644
--- a/setup.py
+++ b/setup.py
@@ -131,7 +131,7 @@ setup(
     ],
     extras_require={
         'email': ['email-validator>=1.0.3'],
-        'typing_extensions': ['typing-extensions>=3.7.2'],
+        'typing_extensions': ['typing-extensions>=3.7.4'],
         'dotenv': ['python-dotenv>=0.10.4'],
     },
     ext_modules=ext_modules,


Test patch:
diff --git a/tests/test_annotated.py b/tests/test_annotated.py
new file mode 100644
index 000000000..4aff92c77
--- /dev/null
+++ b/tests/test_annotated.py
@@ -0,0 +1,136 @@
+import sys
+from typing import get_type_hints
+
+import pytest
+
+from pydantic import BaseModel, Field
+from pydantic.fields import Undefined
+from pydantic.typing import Annotated
+
+
+@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
+@pytest.mark.parametrize(
+    ['hint_fn', 'value'],
+    [
+        # Test Annotated types with arbitrary metadata
+        pytest.param(
+            lambda: Annotated[int, 0],
+            5,
+            id='misc-default',
+        ),
+        pytest.param(
+            lambda: Annotated[int, 0],
+            Field(default=5, ge=0),
+            id='misc-field-default-constraint',
+        ),
+        # Test valid Annotated Field uses
+        pytest.param(
+            lambda: Annotated[int, Field(description='Test')],
+            5,
+            id='annotated-field-value-default',
+        ),
+        pytest.param(
+            lambda: Annotated[int, Field(default_factory=lambda: 5, description='Test')],
+            Undefined,
+            id='annotated-field-default_factory',
+        ),
+    ],
+)
+def test_annotated(hint_fn, value):
+    hint = hint_fn()
+
+    class M(BaseModel):
+        x: hint = value
+
+    assert M().x == 5
+    assert M(x=10).x == 10
+
+    # get_type_hints doesn't recognize typing_extensions.Annotated, so will return the full
+    # annotation. 3.9 w/ stock Annotated will return the wrapped type by default, but return the
+    # full thing with the new include_extras flag.
+    if sys.version_info >= (3, 9):
+        assert get_type_hints(M)['x'] is int
+        assert get_type_hints(M, include_extras=True)['x'] == hint
+    else:
+        assert get_type_hints(M)['x'] == hint
+
+
+@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
+@pytest.mark.parametrize(
+    ['hint_fn', 'value', 'subclass_ctx'],
+    [
+        pytest.param(
+            lambda: Annotated[int, Field(5)],
+            Undefined,
+            pytest.raises(ValueError, match='`Field` default cannot be set in `Annotated`'),
+            id='annotated-field-default',
+        ),
+        pytest.param(
+            lambda: Annotated[int, Field(), Field()],
+            Undefined,
+            pytest.raises(ValueError, match='cannot specify multiple `Annotated` `Field`s'),
+            id='annotated-field-dup',
+        ),
+        pytest.param(
+            lambda: Annotated[int, Field()],
+            Field(),
+            pytest.raises(ValueError, match='cannot specify `Annotated` and value `Field`'),
+            id='annotated-field-value-field-dup',
+        ),
+        pytest.param(
+            lambda: Annotated[int, Field(default_factory=lambda: 5)],  # The factory is not used
+            5,
+            pytest.raises(ValueError, match='cannot specify both default and default_factory'),
+            id='annotated-field-default_factory-value-default',
+        ),
+    ],
+)
+def test_annotated_model_exceptions(hint_fn, value, subclass_ctx):
+    hint = hint_fn()
+    with subclass_ctx:
+
+        class M(BaseModel):
+            x: hint = value
+
+
+@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
+@pytest.mark.parametrize(
+    ['hint_fn', 'value', 'empty_init_ctx'],
+    [
+        pytest.param(
+            lambda: Annotated[int, 0],
+            Undefined,
+            pytest.raises(ValueError, match='field required'),
+            id='misc-no-default',
+        ),
+        pytest.param(
+            lambda: Annotated[int, Field()],
+            Undefined,
+            pytest.raises(ValueError, match='field required'),
+            id='annotated-field-no-default',
+        ),
+    ],
+)
+def test_annotated_instance_exceptions(hint_fn, value, empty_init_ctx):
+    hint = hint_fn()
+
+    class M(BaseModel):
+        x: hint = value
+
+    with empty_init_ctx:
+        assert M().x == 5
+
+
+@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed')
+def test_field_reuse():
+    field = Field(description='Long description')
+
+    class Model(BaseModel):
+        one: int = field
+
+    assert Model(one=1).dict() == {'one': 1}
+
+    class AnnotatedModel(BaseModel):
+        one: Annotated[int, field]
+
+    assert AnnotatedModel(one=1).dict() == {'one': 1}
diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py
index 04d34aa60..5ad7347d1 100644
--- a/tests/test_edge_cases.py
+++ b/tests/test_edge_cases.py
@@ -20,6 +20,11 @@ from pydantic import (
 )
 from pydantic.fields import Field, Schema
 
+try:
+    import cython
+except ImportError:
+    cython = None
+
 
 def test_str_bytes():
     class Model(BaseModel):
@@ -1745,3 +1750,26 @@ def test_default_factory_validator_child():
         pass
 
     assert Child(foo=['a', 'b']).foo == ['a-1', 'b-1']
+
+
+@pytest.mark.skipif(cython is None, reason='cython not installed')
+def test_cython_function_untouched():
+    Model = cython.inline(
+        # language=Python
+        """
+from pydantic import BaseModel
+
+class Model(BaseModel):
+    a = 0.0
+    b = 10
+
+    def get_double_a(self) -> float:
+        return self.a + self.b
+
+return Model
+"""
+    )
+    model = Model(a=10.2)
+    assert model.a == 10.2
+    assert model.b == 10
+    return model.get_double_a() == 20.2
diff --git a/tests/test_json.py b/tests/test_json.py
index 07bfc04f7..06a4764d7 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -170,6 +170,35 @@ def test_custom_iso_timedelta():
     assert m.json() == '{"x": "P0DT0H2M3.000000S"}'
 
 
+def test_json_encoder_simple_inheritance():
+    class Parent(BaseModel):
+        dt: datetime.datetime = datetime.datetime.now()
+        timedt: datetime.timedelta = datetime.timedelta(hours=100)
+
+        class Config:
+            json_encoders = {datetime.datetime: lambda _: 'parent_encoder'}
+
+    class Child(Parent):
+        class Config:
+            json_encoders = {datetime.timedelta: lambda _: 'child_encoder'}
+
+    assert Child().json() == '{"dt": "parent_encoder", "timedt": "child_encoder"}'
+
+
+def test_json_encoder_inheritance_override():
+    class Parent(BaseModel):
+        dt: datetime.datetime = datetime.datetime.now()
+
+        class Config:
+            json_encoders = {datetime.datetime: lambda _: 'parent_encoder'}
+
+    class Child(Parent):
+        class Config:
+            json_encoders = {datetime.datetime: lambda _: 'child_encoder'}
+
+    assert Child().json() == '{"dt": "child_encoder"}'
+
+
 def test_custom_encoder_arg():
     class Model(BaseModel):
         x: datetime.timedelta
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 1850c0f73..f7cd16360 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,3 +1,4 @@
+import collections.abc
 import os
 import re
 import string
@@ -14,11 +15,13 @@ from pydantic.color import Color
 from pydantic.dataclasses import dataclass
 from pydantic.fields import Undefined
 from pydantic.typing import (
+    Annotated,
     ForwardRef,
     Literal,
     all_literal_values,
     display_as_type,
     get_args,
+    get_origin,
     is_new_type,
     new_type_supertype,
     resolve_annotations,
@@ -432,6 +435,24 @@ def test_smart_deepcopy_collection(collection, mocker):
 T = TypeVar('T')
 
 
+@pytest.mark.skipif(sys.version_info < (3, 7), reason='get_origin is only consistent for python >= 3.7')
+@pytest.mark.parametrize(
+    'input_value,output_value',
+    [
+        (Annotated[int, 10] if Annotated else None, Annotated),
+        (Callable[[], T][int], collections.abc.Callable),
+        (Dict[str, int], dict),
+        (List[str], list),
+        (Union[int, str], Union),
+        (int, None),
+    ],
+)
+def test_get_origin(input_value, output_value):
+    if input_value is None:
+        pytest.skip('Skipping undefined hint for this python version')
+    assert get_origin(input_value) is output_value
+
+
 @pytest.mark.skipif(sys.version_info < (3, 8), reason='get_args is only consistent for python >= 3.8')
 @pytest.mark.parametrize(
     'input_value,output_value',
@@ -444,9 +465,12 @@ T = TypeVar('T')
         (Union[int, Union[T, int], str][int], (int, str)),
         (Union[int, Tuple[T, int]][str], (int, Tuple[str, int])),
         (Callable[[], T][int], ([], int)),
+        (Annotated[int, 10] if Annotated else None, (int, 10)),
     ],
 )
 def test_get_args(input_value, output_value):
+    if input_value is None:
+        pytest.skip('Skipping undefined hint for this python version')
     assert get_args(input_value) == output_value