Repository: sanic-org/sanic-ext (python)

Issue:
[Feature Request] Make pydantic models (or dicts) usable for openapi definition
**Is your feature request related to a problem? Please describe your use case.**
Pydantic models are not really usable for openapi definitions. It would be nice to use the models for validation and openapi.
The openapi decorator generates correct output for pydantic models in terms of properties and type, but nothing more. It would be nice to use pydantics `Field` for more details (example value, description, ...).
Pydantic is able to export a schema compatible to the openapi specs with `model.schema()`. This generates a dict, which means it would be enough to support dicts for the openapi definition.

**Describe the solution you'd like**
This should generate an openapi definition with an example field
```python
from sanic import Sanic, json
from sanic_ext import validate, openapi
from pydantic import BaseModel, Field

class Test(BaseModel):
    foo: str = Field(description="Foo Description", example="FOOO")
    bar: str = "test"


app = Sanic("test")

@app.get("/")
@openapi.definition(
    body={'application/json': Test.schema()},
)
@validate(json=Test)
async def get(request):
    return json({})
```

**Additional context**
-


Pull request:
Allow dict schema definitions in body requests
Closes #125 

```python
from sanic import Sanic, json
from sanic_ext import validate, openapi
from pydantic import BaseModel, Field

class Test(BaseModel):
    foo: str = Field(description="Foo Description", example="FOOO")
    bar: str = "test"


app = Sanic("test")

@app.get("/")
@openapi.definition(
    body={'application/json': Test.schema()},
)
@validate(json=Test)
async def get(request):
    return json({})
```

Patch:
diff --git a/sanic_ext/extensions/openapi/definitions.py b/sanic_ext/extensions/openapi/definitions.py
index 69c28cd..878214a 100644
--- a/sanic_ext/extensions/openapi/definitions.py
+++ b/sanic_ext/extensions/openapi/definitions.py
@@ -84,6 +84,12 @@ class MediaType(Definition):
 
     @staticmethod
     def make(value: Any):
+        if isinstance(value, dict):
+            kwargs = {}
+            if "schema" in value:
+                kwargs = {**value}
+                value = kwargs.pop("schema")
+            return MediaType(value, **kwargs)
         return MediaType(Schema.make(value))
 
     @staticmethod


Test patch:
diff --git a/tests/extensions/openapi/test_decorators.py b/tests/extensions/openapi/test_decorators.py
index 7651746..a7c3e7b 100644
--- a/tests/extensions/openapi/test_decorators.py
+++ b/tests/extensions/openapi/test_decorators.py
@@ -406,7 +406,7 @@ def test_tag_decorator(app, decorator, tags):
     assert list(tagged) == list(tags)
 
 
-def test_definition_decorator_body_dict(app):
+def test_definition_decorator_body_dict_w_obj(app):
     @app.route("/")
     @openapi.definition(body={"application/json": Bar})
     async def handler(_):
@@ -425,6 +425,48 @@ def test_definition_decorator_body_dict(app):
     }
 
 
+def test_definition_decorator_body_dict_only_schema_head(app):
+    @app.route("/")
+    @openapi.definition(
+        body={
+            "application/json": {
+                "schema": {
+                    "type": "object",
+                    "properties": {"name": {"type": "string"}},
+                }
+            }
+        }
+    )
+    async def handler(_):
+        ...
+
+    body = get_path(app, "/")["requestBody"]
+    assert body == {
+        "content": {
+            "application/json": {
+                "schema": {
+                    "type": "object",
+                    "properties": {"name": {"type": "string"}},
+                }
+            }
+        }
+    }
+
+
+def test_definition_decorator_body_dict_only_schema_root(app):
+    @app.route("/")
+    @openapi.definition(
+        body={
+            "application/json": {
+                "type": "object",
+                "properties": {"name": {"type": "string"}},
+            }
+        }
+    )
+    async def handler(_):
+        ...
+
+
 def test_definition_decorator_httpmethodview(app):
     class View(HTTPMethodView, uri="/", attach=app):
         @openapi.definition(body={"application/json": Bar})