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

Issue:
OpenAPI does not include properties to an embedded object
**Describe the bug**
It was working nicely in sanic_openapi, but now as we attempt moving to sanic-ext it does not. 

When a schema embeds another in a field as an object, it's not embedded. Instead it embeds an object without properties. 

<img width="499" alt="image" src="https://user-images.githubusercontent.com/11650/208183840-b39fe311-b81b-4a25-97f0-ed7a8261add7.png">

When embedded as an array of objects it works as expected.

<img width="622" alt="image" src="https://user-images.githubusercontent.com/11650/208183944-f6fe6144-6dea-42ae-8098-d1ad187e7326.png">

**Screenshots**

`list_categories`:

<img width="658" alt="image" src="https://user-images.githubusercontent.com/11650/208182721-18452639-0e65-4c59-8bfe-5eb63a0042a7.png">

`get_category`:

<img width="171" alt="image" src="https://user-images.githubusercontent.com/11650/208183055-9aba10cd-2409-4b4e-87e5-c5670920d86c.png">

**To Reproduce**

```python
class Category:
    id = doc.Integer(required=True, example="1")
    name = doc.String(required=True, example="Full-time")
    instruction = doc.String(example="Category instruction template")

class CategoryResponse:
    data = Category


class CategoriesResponse:
    data = [Category]


@app.get("/api/categories")
@doc.response(200, {"application/json": CategoriesResponse}, "Success")
async def list_categories(_request):
    ...

@app.get("/api/categories/<category_id:int>")
@doc.response(200, {"application/json": CategoryResponse}, "Success")
async def get_category(_request, category_id: int):
    ...
```

**Expected behavior**

Embedded schema appears as appropriate.

**Environment (please complete the following information):**
 - OS: Mac OS X
 - Browser Firefox 
 - Version 108b

**Additional context**

Sanic version: 22.9.1
Python 3.10.8
sanic-routing==22.8.0, sanic-testing==22.9.0, sanic-ext==22.9.1


Pull request:
Allow property classes as long as it is not nested definition
Closes #158 

Patch:
diff --git a/sanic_ext/extensions/openapi/types.py b/sanic_ext/extensions/openapi/types.py
index 8fdbb8c..3df27f3 100644
--- a/sanic_ext/extensions/openapi/types.py
+++ b/sanic_ext/extensions/openapi/types.py
@@ -369,6 +369,11 @@ def _properties(value: object) -> Dict:
             k: v
             for k, v in {**fields, **get_type_hints(cls), **extra}.items()
             if not k.startswith("_")
+            and not (
+                isclass(v)
+                and isclass(cls)
+                and v.__qualname__.endswith(f"{cls.__name__}.{v.__name__}")
+            )
         }
     except TypeError:
         return {}
@@ -382,4 +387,4 @@ def _extract(item):
 
 
 def _is_property(item):
-    return not isfunction(item) and not ismethod(item) and not isclass(item)
+    return not isfunction(item) and not ismethod(item)


Test patch:
diff --git a/tests/extensions/openapi/test_schema.py b/tests/extensions/openapi/test_schema.py
index 079f86b..2f0ff92 100644
--- a/tests/extensions/openapi/test_schema.py
+++ b/tests/extensions/openapi/test_schema.py
@@ -3,7 +3,7 @@ from typing import List
 
 import pytest
 
-from sanic_ext.extensions.openapi.types import Schema
+from sanic_ext.extensions.openapi.types import Schema, String
 
 
 @pytest.mark.skipif(version_info < (3, 9), reason="Not needed on 3.8")
@@ -29,6 +29,7 @@ def test_schema_list():
 
     schema = Schema.make(Foo)
     serialized = schema.serialize()
+
     assert "no_show_method" not in serialized
     assert "no_show_classmethod" not in serialized
     assert "no_show_staticmethod" not in serialized
@@ -46,3 +47,34 @@ def test_schema_list():
             "show": {"type": "boolean"},
         },
     }
+
+
+def test_schema_fields():
+    class Pet:
+        name = String(example="Snoopy")
+
+    class Single:
+        pet = Pet
+
+        class Ignore:
+            ...
+
+    class Multiple:
+        pets = [Pet]
+
+    pet_schema = Schema.make(Pet).serialize()
+    single_schema = Schema.make(Single).serialize()
+    multiple_schema = Schema.make(Multiple).serialize()
+
+    assert pet_schema == {
+        "type": "object",
+        "properties": {"name": {"type": "string", "example": "Snoopy"}},
+    }
+    assert single_schema == {
+        "type": "object",
+        "properties": {"pet": pet_schema},
+    }
+    assert multiple_schema == {
+        "type": "object",
+        "properties": {"pets": {"type": "array", "items": pet_schema}},
+    }