Repository: samuelcolvin/pydantic
Pull request #2013 (2020-10-18 19:00:18)

Bump isort from 5.6.1 to 5.6.4
Bumps [isort](https://github.com/pycqa/isort) from 5.6.1 to 5.6.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/pycqa/isort/releases">isort's releases</a>.</em></p>
<blockquote>
<h3>5.6.4 October 12, 2020</h3>
<ul>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1556">#1556</a>: Empty line added between imports that should be skipped.</li>
</ul>
<h3>5.6.3 October 11, 2020</h3>
<ul>
<li>Improved packaging of test files alongside source distribution (see: <a href="https://github-redirect.dependabot.com/PyCQA/isort/pull/1555">PyCQA/isort#1555</a>).</li>
</ul>
<h3>5.6.2 October 10, 2020</h3>
<ul>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1548">#1548</a>: On rare occasions an unecessary empty line can be added when an import is marked as skipped.</li>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1542">#1542</a>: Bug in VERTICAL_PREFIX_FROM_MODULE_IMPORT wrap mode.</li>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1552">#1552</a>: Pylama test dependent on source layout.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md">isort's changelog</a>.</em></p>
<blockquote>
<h3>5.6.4 October 12, 2020</h3>
<ul>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1556">#1556</a>: Empty line added between imports that should be skipped.</li>
</ul>
<h3>5.6.3 October 11, 2020</h3>
<ul>
<li>Improved packaging of test files alongside source distribution (see: <a href="https://github-redirect.dependabot.com/PyCQA/isort/pull/1555">PyCQA/isort#1555</a>).</li>
</ul>
<h3>5.6.2 October 10, 2020</h3>
<ul>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1548">#1548</a>: On rare occasions an unecessary empty line can be added when an import is marked as skipped.</li>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1542">#1542</a>: Bug in VERTICAL_PREFIX_FROM_MODULE_IMPORT wrap mode.</li>
<li>Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1552">#1552</a>: Pylama test dependent on source layout.</li>
</ul>
<h4>Goal Zero: (Tickets related to aspirational goal of achieving 0 regressions for remaining 5.0.0 lifespan):</h4>
<ul>
<li>Zope added to integration test suite</li>
<li>Additional testing of CLI (simulate unseekable streams)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/PyCQA/isort/commit/6bb47b7acc1554ecb59d2855e9110c447162f674"><code>6bb47b7</code></a> Bump version to 5.6.4</li>
<li><a href="https://github.com/PyCQA/isort/commit/a6b253e91e305d1b2581abb113cfdcc4073c6605"><code>a6b253e</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1557">#1557</a> from PyCQA/issue/1556/remove-uneeded-new-line</li>
<li><a href="https://github.com/PyCQA/isort/commit/b4878742ff3f6c9b3e46c329ddbdbdeda8553d9e"><code>b487874</code></a> formatting fix</li>
<li><a href="https://github.com/PyCQA/isort/commit/54187d0a4b652b856c59acb90aa963544d537984"><code>54187d0</code></a> Merge branch 'master' of <a href="https://github.com/timothycrosley/isort">https://github.com/timothycrosley/isort</a> into issue/1...</li>
<li><a href="https://github.com/PyCQA/isort/commit/49bb9babe3d2fb268b95d4170d5fc1dc267c213d"><code>49bb9ba</code></a> Fixed <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1556">#1556</a>: Empty line added between imports that should be skipped.</li>
<li><a href="https://github.com/PyCQA/isort/commit/491b3461d572249de71a7b2572af281322d4f40b"><code>491b346</code></a> Bump version to 5.6.3</li>
<li><a href="https://github.com/PyCQA/isort/commit/383f323e44da4065a069c4817817e56bcfa7d546"><code>383f323</code></a> Update changelog to include source distribution improvement</li>
<li><a href="https://github.com/PyCQA/isort/commit/b662f440ac859fb6b5fcfacea6cb94052a873388"><code>b662f44</code></a> Add James Curtin (<a href="https://github.com/jamescurtin">@jamescurtin</a>) to acknowledgements</li>
<li><a href="https://github.com/PyCQA/isort/commit/fb0cdf74b75b190b5c90d0fafe78d7ab54f201d3"><code>fb0cdf7</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/pycqa/isort/issues/1555">#1555</a> from sbraz/tests</li>
<li><a href="https://github.com/PyCQA/isort/commit/66d6345171b398c9e025904789d121e78dcad79a"><code>66d6345</code></a> Specify minimum build system</li>
<li>Additional commits viewable in <a href="https://github.com/pycqa/isort/compare/5.6.1...5.6.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=isort&package-manager=pip&previous-version=5.6.1&new-version=5.6.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/configuring-github-dependabot-security-updates)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

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


```diff
@@            Coverage Diff             @@
##           master    #2013      +/-   ##
==========================================
- Coverage   99.69%   99.15%   -0.54%     
==========================================
  Files          21       21              
  Lines        3925     3925              
  Branches      788      788              
==========================================
- Hits         3913     3892      -21     
- Misses         10       27      +17     
- Partials        2        6       +4     
```


| [Impacted Files](https://codecov.io/gh/samuelcolvin/pydantic/pull/2013?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pydantic/decorator.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2013/diff?src=pr&el=tree#diff-cHlkYW50aWMvZGVjb3JhdG9yLnB5) | `87.42% <0.00%> (-12.58%)` | :arrow_down: |
| [pydantic/typing.py](https://codecov.io/gh/samuelcolvin/pydantic/pull/2013/diff?src=pr&el=tree#diff-cHlkYW50aWMvdHlwaW5nLnB5) | `97.16% <0.00%> (-0.95%)` | :arrow_down: |

------

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


Patch:
diff --git a/changes/1748-PrettyWood.md b/changes/1748-PrettyWood.md
new file mode 100644
index 000000000..e2d366e07
--- /dev/null
+++ b/changes/1748-PrettyWood.md
@@ -0,0 +1,1 @@
+Support custom title, description and default in schema of enums
\ No newline at end of file
diff --git a/pydantic/schema.py b/pydantic/schema.py
index 15b506678..a7c105f9a 100644
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -149,6 +149,30 @@ def model_schema(
     return m_schema
 
 
+def get_field_info_schema(field: ModelField) -> Tuple[Dict[str, Any], bool]:
+    schema_overrides = False
+
+    # If no title is explicitly set, we don't set title in the schema for enums.
+    # The behaviour is the same as `BaseModel` reference, where the default title
+    # is in the definitions part of the schema.
+    schema: Dict[str, Any] = {}
+    if field.field_info.title or not lenient_issubclass(field.type_, Enum):
+        schema['title'] = field.field_info.title or field.alias.title().replace('_', ' ')
+
+    if field.field_info.title:
+        schema_overrides = True
+
+    if field.field_info.description:
+        schema['description'] = field.field_info.description
+        schema_overrides = True
+
+    if not field.required and not field.field_info.const and field.default is not None:
+        schema['default'] = encode_default(field.default)
+        schema_overrides = True
+
+    return schema, schema_overrides
+
+
 def field_schema(
     field: ModelField,
     *,
@@ -172,18 +196,7 @@ def field_schema(
     :return: tuple of the schema for this field and additional definitions
     """
     ref_prefix = ref_prefix or default_prefix
-    schema_overrides = False
-    s = dict(title=field.field_info.title or field.alias.title().replace('_', ' '))
-    if field.field_info.title:
-        schema_overrides = True
-
-    if field.field_info.description:
-        s['description'] = field.field_info.description
-        schema_overrides = True
-
-    if not field.required and not field.field_info.const and field.default is not None:
-        s['default'] = encode_default(field.default)
-        schema_overrides = True
+    s, schema_overrides = get_field_info_schema(field)
 
     validation_schema = get_field_schema_validations(field)
     if validation_schema:
@@ -228,6 +241,11 @@ def get_field_schema_validations(field: ModelField) -> Dict[str, Any]:
     a Pydantic ``FieldInfo`` with validation arguments.
     """
     f_schema: Dict[str, Any] = {}
+
+    if lenient_issubclass(field.type_, Enum):
+        # schema is already updated by `enum_process_schema`
+        return f_schema
+
     if lenient_issubclass(field.type_, (str, bytes)):
         for attr_name, t, keyword in _str_types_attrs:
             attr = getattr(field.field_info, attr_name, None)
@@ -651,6 +669,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}
+    return {'allOf': [schema_ref]} if schema_overrides else schema_ref
+
+
 def field_singleton_schema(  # noqa: C901 (ignore complexity)
     field: ModelField,
     *,
@@ -703,7 +726,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
 
     if lenient_issubclass(field_type, Enum):
         enum_name = normalize_name(field_type.__name__)
-        f_schema = {'$ref': ref_prefix + enum_name}
+        f_schema, schema_overrides = get_field_info_schema(field)
+        f_schema.update(get_schema_ref(ref_prefix + enum_name, schema_overrides))
         definitions[enum_name] = enum_process_schema(field_type)
     else:
         add_field_type_to_schema(field_type, f_schema)
@@ -734,11 +758,8 @@ def field_singleton_schema(  # noqa: C901 (ignore complexity)
             nested_models.update(sub_nested_models)
         else:
             nested_models.add(model_name)
-        schema_ref = {'$ref': ref_prefix + model_name}
-        if not schema_overrides:
-            return schema_ref, definitions, nested_models
-        else:
-            return {'allOf': [schema_ref]}, definitions, nested_models
+        schema_ref = get_schema_ref(ref_prefix + model_name, schema_overrides)
+        return schema_ref, definitions, nested_models
 
     raise ValueError(f'Value not declarable with JSON Schema, field: {field}')
 


Test patch:
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 67dd53287..8fee4e032 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -2,7 +2,7 @@ black==20.8b1
 coverage==5.3
 flake8==3.8.4
 flake8-quotes==3.2.0
-isort==5.6.1
+isort==5.6.4
 mypy==0.790
 pycodestyle==2.6.0
 pyflakes==2.2.0
diff --git a/tests/test_schema.py b/tests/test_schema.py
index b08690ef2..ef5d2b0eb 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -250,6 +250,103 @@ def test_enum_modify_schema():
     }
 
 
+def test_enum_schema_custom_field():
+    class FooBarEnum(str, Enum):
+        foo = 'foo'
+        bar = 'bar'
+
+    class Model(BaseModel):
+        pika: FooBarEnum = Field(alias='pikalias', title='Pikapika!', description='Pika is definitely the best!')
+        bulbi: FooBarEnum = Field('foo', alias='bulbialias', title='Bulbibulbi!', description='Bulbi is not...')
+        cara: FooBarEnum
+
+    assert Model.schema() == {
+        'definitions': {
+            'FooBarEnum': {
+                'description': 'An enumeration.',
+                'enum': ['foo', 'bar'],
+                'title': 'FooBarEnum',
+                'type': 'string',
+            }
+        },
+        'properties': {
+            'pikalias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Pika is definitely the best!',
+                'title': 'Pikapika!',
+            },
+            'bulbialias': {
+                'allOf': [{'$ref': '#/definitions/FooBarEnum'}],
+                'description': 'Bulbi is not...',
+                'title': 'Bulbibulbi!',
+                'default': 'foo',
+            },
+            'cara': {'$ref': '#/definitions/FooBarEnum'},
+        },
+        'required': ['pikalias', 'cara'],
+        'title': 'Model',
+        'type': 'object',
+    }
+
+
+def test_enum_and_model_have_same_behaviour():
+    class Names(str, Enum):
+        rick = 'Rick'
+        morty = 'Morty'
+        summer = 'Summer'
+
+    class Pika(BaseModel):
+        a: str
+
+    class Foo(BaseModel):
+        enum: Names
+        titled_enum: Names = Field(
+            ...,
+            title='Title of enum',
+            description='Description of enum',
+        )
+        model: Pika
+        titled_model: Pika = Field(
+            ...,
+            title='Title of model',
+            description='Description of model',
+        )
+
+    assert Foo.schema() == {
+        'definitions': {
+            'Pika': {
+                'properties': {'a': {'title': 'A', 'type': 'string'}},
+                'required': ['a'],
+                'title': 'Pika',
+                'type': 'object',
+            },
+            'Names': {
+                'description': 'An enumeration.',
+                'enum': ['Rick', 'Morty', 'Summer'],
+                'title': 'Names',
+                'type': 'string',
+            },
+        },
+        'properties': {
+            'enum': {'$ref': '#/definitions/Names'},
+            'model': {'$ref': '#/definitions/Pika'},
+            'titled_enum': {
+                'allOf': [{'$ref': '#/definitions/Names'}],
+                'description': 'Description of enum',
+                'title': 'Title of enum',
+            },
+            'titled_model': {
+                'allOf': [{'$ref': '#/definitions/Pika'}],
+                'description': 'Description of model',
+                'title': 'Title of model',
+            },
+        },
+        'required': ['enum', 'titled_enum', 'model', 'titled_model'],
+        'title': 'Foo',
+        'type': 'object',
+    }
+
+
 def test_json_schema():
     class Model(BaseModel):
         a = b'foobar'