Repository: Algebra8/pyopenapi3

Issue:
Add connexion structure to /examples directory
Include everything that is required to make a simple connexion example work around #62, such as `app.py`.

Discussion:

Patch:
diff --git a/examples/connexion_example/ex.py b/examples/connexion_example/app.py
similarity index 62%
rename from examples/connexion_example/ex.py
rename to examples/connexion_example/app.py
index 18e2a3e..42b4eb3 100644
--- a/examples/connexion_example/ex.py
+++ b/examples/connexion_example/app.py
@@ -1,8 +1,24 @@
-from pyopenapi3 import OpenApiBuilder, create_schema
+"""Example usage of `connexion`.
+
+Connexion parts are taken from (https://github.com/hjacobs/connexion-
+example/blob/master/app.py).
+"""
+
+import os
+from typing import Optional, Dict, List, Any, Tuple, Union
+import datetime
+import logging
+from pathlib import Path
 
+import connexion
+from connexion import NoContent
+
+from pyopenapi3 import OpenApiBuilder, create_schema
 from pyopenapi3.data_types import String, Int32, Array, DateTime, Object
 from pyopenapi3.objects import Op, Response, RequestBody, JSONMediaType
 
+
+# pyopenapi3
 open_bldr = OpenApiBuilder()
 
 
@@ -75,7 +91,7 @@ class Pets:
         )
     ]
 
-    @paths.op(tags=["Pets"], operation_id=["app.get_pets"])
+    @paths.op(tags=["Pets"], operation_id="app.get_pets")
     @paths.query_param(
         name="animal_type",
         schema=create_schema(String, pattern="^[a-zA-Z0-9]*$")
@@ -108,7 +124,7 @@ class PetsWithId:
         Response(status=404, description="Pet does not exist")
     ]
 
-    @paths.op(tags=["Pets"], operation_id=["app.get_pet"])
+    @paths.op(tags=["Pets"], operation_id="app.get_pet")
     def get(self) -> Op[..., get_responses]:
         """Get a single pet"""
 
@@ -124,7 +140,7 @@ class PetsWithId:
         required=True
     )
 
-    @paths.op(tags=["Pets"], operation_id=["app.put_get"])
+    @paths.op(tags=["Pets"], operation_id="app.put_get")
     def put(self) -> Op[put_body, put_responses]:
         """Create or update a pet"""
 
@@ -133,6 +149,71 @@ class PetsWithId:
         Response(status=404, description="Pet does not exist")
     ]
 
-    @paths.op(tags=["Pets"], operation_id=["app.delete_pet"])
+    @paths.op(tags=["Pets"], operation_id="app.delete_pet")
     def delete(self) -> Op[..., delete_responses]:
         """Remove a pet"""
+
+
+# Connexion
+Pet = Dict[str, Any]
+Response = Tuple[str, int]
+
+PETS: Dict[str, Pet] = {}
+
+
+def get_pets(
+    limit: int,
+    animal_type: Optional[str] = None
+) -> Dict[str, List[Pet]]:
+    return {
+        'pets': [
+            pet for pet in PETS.values()
+            if animal_type is None or
+            pet['animal_type'] == animal_type[:limit]
+        ]
+    }
+
+
+def get_pet(pet_id: str) -> Union[Pet, Response]:
+    return PETS.get(pet_id, False) or ('Not found', 404)
+
+
+def put_get(pet_id: str, pet: Pet) -> Response:
+    exists = pet_id in PETS
+    pet['id'] = pet_id
+
+    if exists:
+        logging.info(f'Updating pet {pet_id}..')
+        PETS[pet_id].update(pet)
+    else:
+        logging.info(f'Creating pet {pet_id}..')
+        pet['created'] = datetime.datetime.utcnow()
+        PETS[pet_id] = pet
+    return NoContent, (200 if exists else 201)
+
+
+def delete_pet(pet_id: str) -> Response:
+    if pet_id in PETS:
+        logging.info(f'Deleting pet {pet_id}..')
+        del PETS[pet_id]
+        return NoContent, 204
+    else:
+        return NoContent, 404
+
+
+logging.basicConfig(level=logging.INFO)
+app = connexion.App(__name__)
+
+s = 'swagger.yaml'
+swagger_dir = os.path.abspath(os.path.dirname(__file__))
+swagger_path = Path(swagger_dir) / s
+with open(swagger_path, 'w') as f:
+    f.write(open_bldr.yaml())
+
+app.add_api(s)
+application = app.app
+
+
+if __name__ == '__main__':
+    print("Beginning server...")
+    app.run(port=8080, server='gevent')
diff --git a/src/pyopenapi3/builders.py b/src/pyopenapi3/builders.py
index a4b1663..901819c 100644
--- a/src/pyopenapi3/builders.py
+++ b/src/pyopenapi3/builders.py
@@ -301,6 +301,11 @@ class ParamBuilder:
         if 'schema' in kwargs:
             schema = kwargs.pop('schema')
             kwargs['schema'] = create_schema(schema)
+            # If the schema is a reference, then return
+            # the reference.
+            if type(schema) == type:
+                if issubclass(schema, Component):
+                    return kwargs['schema']
         elif 'content' in kwargs:
             content = kwargs.pop('content')
             kwargs['content'] = build_mediatype_schema_from_content(content)
diff --git a/src/pyopenapi3/utils.py b/src/pyopenapi3/utils.py
index ff70d52..b6bf5c1 100644
--- a/src/pyopenapi3/utils.py
+++ b/src/pyopenapi3/utils.py
@@ -119,7 +119,7 @@ def format_description(s: Optional[str]) -> Optional[str]:
 def parse_name_and_type_from_fmt_str(
         formatted_str: str,
         allowed_types: Optional[Dict[str, Component]] = None
-) -> Generator[Tuple[str, Union[Type[Field], str]], None, None]:
+) -> Generator[Tuple[str, Type[Field]], None, None]:
     """
     Parse a formatted string and return the names of the args
     and their types. Will raise a ValueError if the type is not
@@ -141,7 +141,7 @@ def parse_name_and_type_from_fmt_str(
         @open_bldr.component.parameter
         class PetId: ...
 
-        "/pets/{pet:PetId}" -> ("pet", "PetId")
+        "/pets/{pet:PetId}" -> ("pet", PetId)
 
     If the string is not formatted, then will return (None, None).
     """


Test patch:
diff --git a/tests/examples/component.py b/tests/examples/component.py
index 50552e2..dc80eea 100644
--- a/tests/examples/component.py
+++ b/tests/examples/component.py
@@ -150,3 +150,18 @@ param_reference_comp = {
     }
   }
 }
+
+param_component = {
+  "parameters": {
+    "PetID": {
+      "name": "pet_id",
+      "in": "path",
+      "description": "Pet's Unique identifier",
+      "required": True,
+      "schema": {
+        "pattern": "^[a-zA-Z0-9-]+$",
+        "type": "string"
+      }
+    }
+  }
+}
diff --git a/tests/examples/path.py b/tests/examples/path.py
index 753d6fc..a551abc 100644
--- a/tests/examples/path.py
+++ b/tests/examples/path.py
@@ -21,13 +21,19 @@ path = {
   }
 }
 
-path_with_parameter = {
+global_path_with_reference_parameter = {
+  'parameters': [
+    {'$ref': '#/components/parameters/PetID'}
+  ]
+}
+
+global_path_with_schema_parameter = {
   'parameters': [
     {
-      'name': 'pet_id',
+      'name': 'pet_name',
       'in': 'path',
       'required': True,
-      'schema': {'$ref': '#/components/parameters/PetID'}
+      'schema': {'type': 'string'}
     }
   ]
 }
diff --git a/tests/test_builders.py b/tests/test_builders.py
index 339e93e..a5e768e 100644
--- a/tests/test_builders.py
+++ b/tests/test_builders.py
@@ -181,13 +181,29 @@ def test_path_with_path_parameter():
         required = True
 
     @open_bldr.path
-    class Path:
+    class PathWithReference:
 
         path = "/pets/{pet_id:PetID}"
 
-    p = open_bldr.path.build["/pets/{pet_id}"]
+    @open_bldr.path
+    class PathWithSchema:
+
+        path = "/pets/{pet_name:String}"
+
+    path_with_ref = open_bldr.path.build["/pets/{pet_id}"]
+    path_with_schema = open_bldr.path.build["/pets/{pet_name}"]
 
-    assert p.dict() == path_examples.path_with_parameter
+    assert (
+        path_with_ref.dict()
+        == path_examples.global_path_with_reference_parameter
+    )
+    assert (
+        path_with_schema == path_examples.global_path_with_schema_parameter
+    )
+
+    assert (
+        open_bldr.component.build.dict() == component_examples.param_component
+        )
 
 
 def test_paths_path__break(_allowed_types):