We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Endpoint path parameters type specification compatibility with OAS
When specifying a convertor type for a path parameter like so:
```
...
Route('/users/{user_id:int}', user, methods=["GET", "POST"])
...
```

The OAS schema generated using `SchemaGenerator` interprets the whole portion within `{}`  eg. `'user_id:int'` as the required path parameter variable name, which then requires the parameter name in OAS to be `user_id:int` instead of just `user_id`.

![user_idint](https://user-images.githubusercontent.com/26809407/70369654-50312280-18eb-11ea-9089-53743115394c.png)

I think the convertor is great in that a GET request to `/users/foo` will result in `404: NOT FOUND`. Of course, another option is to have a `try-except` clause to handle non-integer values within the method... but I was wondering what the community thinks about this and if this even should be supported by the `SchemaGenerator`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `starlette/schemas.py`
Content:
```
1 import inspect
2 import typing
3 
4 from starlette.requests import Request
5 from starlette.responses import Response
6 from starlette.routing import BaseRoute, Mount, Route
7 
8 try:
9     import yaml
10 except ImportError:  # pragma: nocover
11     yaml = None  # type: ignore
12 
13 
14 class OpenAPIResponse(Response):
15     media_type = "application/vnd.oai.openapi"
16 
17     def render(self, content: typing.Any) -> bytes:
18         assert yaml is not None, "`pyyaml` must be installed to use OpenAPIResponse."
19         assert isinstance(
20             content, dict
21         ), "The schema passed to OpenAPIResponse should be a dictionary."
22         return yaml.dump(content, default_flow_style=False).encode("utf-8")
23 
24 
25 class EndpointInfo(typing.NamedTuple):
26     path: str
27     http_method: str
28     func: typing.Callable
29 
30 
31 class BaseSchemaGenerator:
32     def get_schema(self, routes: typing.List[BaseRoute]) -> dict:
33         raise NotImplementedError()  # pragma: no cover
34 
35     def get_endpoints(
36         self, routes: typing.List[BaseRoute]
37     ) -> typing.List[EndpointInfo]:
38         """
39         Given the routes, yields the following information:
40 
41         - path
42             eg: /users/
43         - http_method
44             one of 'get', 'post', 'put', 'patch', 'delete', 'options'
45         - func
46             method ready to extract the docstring
47         """
48         endpoints_info: list = []
49 
50         for route in routes:
51             if isinstance(route, Mount):
52                 routes = route.routes or []
53                 sub_endpoints = [
54                     EndpointInfo(
55                         path="".join((route.path, sub_endpoint.path)),
56                         http_method=sub_endpoint.http_method,
57                         func=sub_endpoint.func,
58                     )
59                     for sub_endpoint in self.get_endpoints(routes)
60                 ]
61                 endpoints_info.extend(sub_endpoints)
62 
63             elif not isinstance(route, Route) or not route.include_in_schema:
64                 continue
65 
66             elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):
67                 for method in route.methods or ["GET"]:
68                     if method == "HEAD":
69                         continue
70                     endpoints_info.append(
71                         EndpointInfo(route.path, method.lower(), route.endpoint)
72                     )
73             else:
74                 for method in ["get", "post", "put", "patch", "delete", "options"]:
75                     if not hasattr(route.endpoint, method):
76                         continue
77                     func = getattr(route.endpoint, method)
78                     endpoints_info.append(
79                         EndpointInfo(route.path, method.lower(), func)
80                     )
81 
82         return endpoints_info
83 
84     def parse_docstring(self, func_or_method: typing.Callable) -> dict:
85         """
86         Given a function, parse the docstring as YAML and return a dictionary of info.
87         """
88         docstring = func_or_method.__doc__
89         if not docstring:
90             return {}
91 
92         assert yaml is not None, "`pyyaml` must be installed to use parse_docstring."
93 
94         # We support having regular docstrings before the schema
95         # definition. Here we return just the schema part from
96         # the docstring.
97         docstring = docstring.split("---")[-1]
98 
99         parsed = yaml.safe_load(docstring)
100 
101         if not isinstance(parsed, dict):
102             # A regular docstring (not yaml formatted) can return
103             # a simple string here, which wouldn't follow the schema.
104             return {}
105 
106         return parsed
107 
108     def OpenAPIResponse(self, request: Request) -> Response:
109         routes = request.app.routes
110         schema = self.get_schema(routes=routes)
111         return OpenAPIResponse(schema)
112 
113 
114 class SchemaGenerator(BaseSchemaGenerator):
115     def __init__(self, base_schema: dict) -> None:
116         self.base_schema = base_schema
117 
118     def get_schema(self, routes: typing.List[BaseRoute]) -> dict:
119         schema = dict(self.base_schema)
120         schema.setdefault("paths", {})
121         endpoints_info = self.get_endpoints(routes)
122 
123         for endpoint in endpoints_info:
124 
125             parsed = self.parse_docstring(endpoint.func)
126 
127             if not parsed:
128                 continue
129 
130             if endpoint.path not in schema["paths"]:
131                 schema["paths"][endpoint.path] = {}
132 
133             schema["paths"][endpoint.path][endpoint.http_method] = parsed
134 
135         return schema
136
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/starlette/schemas.py b/starlette/schemas.py
--- a/starlette/schemas.py
+++ b/starlette/schemas.py
@@ -1,4 +1,5 @@
 import inspect
+import re
 import typing
 
 from starlette.requests import Request
@@ -49,10 +50,11 @@
 
         for route in routes:
             if isinstance(route, Mount):
+                path = self._remove_converter(route.path)
                 routes = route.routes or []
                 sub_endpoints = [
                     EndpointInfo(
-                        path="".join((route.path, sub_endpoint.path)),
+                        path="".join((path, sub_endpoint.path)),
                         http_method=sub_endpoint.http_method,
                         func=sub_endpoint.func,
                     )
@@ -64,23 +66,32 @@
                 continue
 
             elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):
+                path = self._remove_converter(route.path)
                 for method in route.methods or ["GET"]:
                     if method == "HEAD":
                         continue
                     endpoints_info.append(
-                        EndpointInfo(route.path, method.lower(), route.endpoint)
+                        EndpointInfo(path, method.lower(), route.endpoint)
                     )
             else:
+                path = self._remove_converter(route.path)
                 for method in ["get", "post", "put", "patch", "delete", "options"]:
                     if not hasattr(route.endpoint, method):
                         continue
                     func = getattr(route.endpoint, method)
-                    endpoints_info.append(
-                        EndpointInfo(route.path, method.lower(), func)
-                    )
+                    endpoints_info.append(EndpointInfo(path, method.lower(), func))
 
         return endpoints_info
 
+    def _remove_converter(self, path: str) -> str:
+        """
+        Remove the converter from the path.
+        For example, a route like this:
+            Route("/users/{id:int}", endpoint=get_user, methods=["GET"])
+        Should be represented as `/users/{id}` in the OpenAPI schema.
+        """
+        return re.sub(r":\w+}", "}", path)
+
     def parse_docstring(self, func_or_method: typing.Callable) -> dict:
         """
         Given a function, parse the docstring as YAML and return a dictionary of info.

{"golden_diff": "diff --git a/starlette/schemas.py b/starlette/schemas.py\n--- a/starlette/schemas.py\n+++ b/starlette/schemas.py\n@@ -1,4 +1,5 @@\n import inspect\n+import re\n import typing\n \n from starlette.requests import Request\n@@ -49,10 +50,11 @@\n \n         for route in routes:\n             if isinstance(route, Mount):\n+                path = self._remove_converter(route.path)\n                 routes = route.routes or []\n                 sub_endpoints = [\n                     EndpointInfo(\n-                        path=\"\".join((route.path, sub_endpoint.path)),\n+                        path=\"\".join((path, sub_endpoint.path)),\n                         http_method=sub_endpoint.http_method,\n                         func=sub_endpoint.func,\n                     )\n@@ -64,23 +66,32 @@\n                 continue\n \n             elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):\n+                path = self._remove_converter(route.path)\n                 for method in route.methods or [\"GET\"]:\n                     if method == \"HEAD\":\n                         continue\n                     endpoints_info.append(\n-                        EndpointInfo(route.path, method.lower(), route.endpoint)\n+                        EndpointInfo(path, method.lower(), route.endpoint)\n                     )\n             else:\n+                path = self._remove_converter(route.path)\n                 for method in [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"]:\n                     if not hasattr(route.endpoint, method):\n                         continue\n                     func = getattr(route.endpoint, method)\n-                    endpoints_info.append(\n-                        EndpointInfo(route.path, method.lower(), func)\n-                    )\n+                    endpoints_info.append(EndpointInfo(path, method.lower(), func))\n \n         return endpoints_info\n \n+    def _remove_converter(self, path: str) -> str:\n+        \"\"\"\n+        Remove the converter from the path.\n+        For example, a route like this:\n+            Route(\"/users/{id:int}\", endpoint=get_user, methods=[\"GET\"])\n+        Should be represented as `/users/{id}` in the OpenAPI schema.\n+        \"\"\"\n+        return re.sub(r\":\\w+}\", \"}\", path)\n+\n     def parse_docstring(self, func_or_method: typing.Callable) -> dict:\n         \"\"\"\n         Given a function, parse the docstring as YAML and return a dictionary of info.\n", "issue": "Endpoint path parameters type specification compatibility with OAS\nWhen specifying a convertor type for a path parameter like so:\r\n```\r\n...\r\nRoute('/users/{user_id:int}', user, methods=[\"GET\", \"POST\"])\r\n...\r\n```\r\n\r\nThe OAS schema generated using `SchemaGenerator` interprets the whole portion within `{}`  eg. `'user_id:int'` as the required path parameter variable name, which then requires the parameter name in OAS to be `user_id:int` instead of just `user_id`.\r\n\r\n![user_idint](https://user-images.githubusercontent.com/26809407/70369654-50312280-18eb-11ea-9089-53743115394c.png)\r\n\r\nI think the convertor is great in that a GET request to `/users/foo` will result in `404: NOT FOUND`. Of course, another option is to have a `try-except` clause to handle non-integer values within the method... but I was wondering what the community thinks about this and if this even should be supported by the `SchemaGenerator`.\n", "before_files": [{"content": "import inspect\nimport typing\n\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import BaseRoute, Mount, Route\n\ntry:\n    import yaml\nexcept ImportError:  # pragma: nocover\n    yaml = None  # type: ignore\n\n\nclass OpenAPIResponse(Response):\n    media_type = \"application/vnd.oai.openapi\"\n\n    def render(self, content: typing.Any) -> bytes:\n        assert yaml is not None, \"`pyyaml` must be installed to use OpenAPIResponse.\"\n        assert isinstance(\n            content, dict\n        ), \"The schema passed to OpenAPIResponse should be a dictionary.\"\n        return yaml.dump(content, default_flow_style=False).encode(\"utf-8\")\n\n\nclass EndpointInfo(typing.NamedTuple):\n    path: str\n    http_method: str\n    func: typing.Callable\n\n\nclass BaseSchemaGenerator:\n    def get_schema(self, routes: typing.List[BaseRoute]) -> dict:\n        raise NotImplementedError()  # pragma: no cover\n\n    def get_endpoints(\n        self, routes: typing.List[BaseRoute]\n    ) -> typing.List[EndpointInfo]:\n        \"\"\"\n        Given the routes, yields the following information:\n\n        - path\n            eg: /users/\n        - http_method\n            one of 'get', 'post', 'put', 'patch', 'delete', 'options'\n        - func\n            method ready to extract the docstring\n        \"\"\"\n        endpoints_info: list = []\n\n        for route in routes:\n            if isinstance(route, Mount):\n                routes = route.routes or []\n                sub_endpoints = [\n                    EndpointInfo(\n                        path=\"\".join((route.path, sub_endpoint.path)),\n                        http_method=sub_endpoint.http_method,\n                        func=sub_endpoint.func,\n                    )\n                    for sub_endpoint in self.get_endpoints(routes)\n                ]\n                endpoints_info.extend(sub_endpoints)\n\n            elif not isinstance(route, Route) or not route.include_in_schema:\n                continue\n\n            elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):\n                for method in route.methods or [\"GET\"]:\n                    if method == \"HEAD\":\n                        continue\n                    endpoints_info.append(\n                        EndpointInfo(route.path, method.lower(), route.endpoint)\n                    )\n            else:\n                for method in [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"]:\n                    if not hasattr(route.endpoint, method):\n                        continue\n                    func = getattr(route.endpoint, method)\n                    endpoints_info.append(\n                        EndpointInfo(route.path, method.lower(), func)\n                    )\n\n        return endpoints_info\n\n    def parse_docstring(self, func_or_method: typing.Callable) -> dict:\n        \"\"\"\n        Given a function, parse the docstring as YAML and return a dictionary of info.\n        \"\"\"\n        docstring = func_or_method.__doc__\n        if not docstring:\n            return {}\n\n        assert yaml is not None, \"`pyyaml` must be installed to use parse_docstring.\"\n\n        # We support having regular docstrings before the schema\n        # definition. Here we return just the schema part from\n        # the docstring.\n        docstring = docstring.split(\"---\")[-1]\n\n        parsed = yaml.safe_load(docstring)\n\n        if not isinstance(parsed, dict):\n            # A regular docstring (not yaml formatted) can return\n            # a simple string here, which wouldn't follow the schema.\n            return {}\n\n        return parsed\n\n    def OpenAPIResponse(self, request: Request) -> Response:\n        routes = request.app.routes\n        schema = self.get_schema(routes=routes)\n        return OpenAPIResponse(schema)\n\n\nclass SchemaGenerator(BaseSchemaGenerator):\n    def __init__(self, base_schema: dict) -> None:\n        self.base_schema = base_schema\n\n    def get_schema(self, routes: typing.List[BaseRoute]) -> dict:\n        schema = dict(self.base_schema)\n        schema.setdefault(\"paths\", {})\n        endpoints_info = self.get_endpoints(routes)\n\n        for endpoint in endpoints_info:\n\n            parsed = self.parse_docstring(endpoint.func)\n\n            if not parsed:\n                continue\n\n            if endpoint.path not in schema[\"paths\"]:\n                schema[\"paths\"][endpoint.path] = {}\n\n            schema[\"paths\"][endpoint.path][endpoint.http_method] = parsed\n\n        return schema\n", "path": "starlette/schemas.py"}], "after_files": [{"content": "import inspect\nimport re\nimport typing\n\nfrom starlette.requests import Request\nfrom starlette.responses import Response\nfrom starlette.routing import BaseRoute, Mount, Route\n\ntry:\n    import yaml\nexcept ImportError:  # pragma: nocover\n    yaml = None  # type: ignore\n\n\nclass OpenAPIResponse(Response):\n    media_type = \"application/vnd.oai.openapi\"\n\n    def render(self, content: typing.Any) -> bytes:\n        assert yaml is not None, \"`pyyaml` must be installed to use OpenAPIResponse.\"\n        assert isinstance(\n            content, dict\n        ), \"The schema passed to OpenAPIResponse should be a dictionary.\"\n        return yaml.dump(content, default_flow_style=False).encode(\"utf-8\")\n\n\nclass EndpointInfo(typing.NamedTuple):\n    path: str\n    http_method: str\n    func: typing.Callable\n\n\nclass BaseSchemaGenerator:\n    def get_schema(self, routes: typing.List[BaseRoute]) -> dict:\n        raise NotImplementedError()  # pragma: no cover\n\n    def get_endpoints(\n        self, routes: typing.List[BaseRoute]\n    ) -> typing.List[EndpointInfo]:\n        \"\"\"\n        Given the routes, yields the following information:\n\n        - path\n            eg: /users/\n        - http_method\n            one of 'get', 'post', 'put', 'patch', 'delete', 'options'\n        - func\n            method ready to extract the docstring\n        \"\"\"\n        endpoints_info: list = []\n\n        for route in routes:\n            if isinstance(route, Mount):\n                path = self._remove_converter(route.path)\n                routes = route.routes or []\n                sub_endpoints = [\n                    EndpointInfo(\n                        path=\"\".join((path, sub_endpoint.path)),\n                        http_method=sub_endpoint.http_method,\n                        func=sub_endpoint.func,\n                    )\n                    for sub_endpoint in self.get_endpoints(routes)\n                ]\n                endpoints_info.extend(sub_endpoints)\n\n            elif not isinstance(route, Route) or not route.include_in_schema:\n                continue\n\n            elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):\n                path = self._remove_converter(route.path)\n                for method in route.methods or [\"GET\"]:\n                    if method == \"HEAD\":\n                        continue\n                    endpoints_info.append(\n                        EndpointInfo(path, method.lower(), route.endpoint)\n                    )\n            else:\n                path = self._remove_converter(route.path)\n                for method in [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\"]:\n                    if not hasattr(route.endpoint, method):\n                        continue\n                    func = getattr(route.endpoint, method)\n                    endpoints_info.append(EndpointInfo(path, method.lower(), func))\n\n        return endpoints_info\n\n    def _remove_converter(self, path: str) -> str:\n        \"\"\"\n        Remove the converter from the path.\n        For example, a route like this:\n            Route(\"/users/{id:int}\", endpoint=get_user, methods=[\"GET\"])\n        Should be represented as `/users/{id}` in the OpenAPI schema.\n        \"\"\"\n        return re.sub(r\":\\w+}\", \"}\", path)\n\n    def parse_docstring(self, func_or_method: typing.Callable) -> dict:\n        \"\"\"\n        Given a function, parse the docstring as YAML and return a dictionary of info.\n        \"\"\"\n        docstring = func_or_method.__doc__\n        if not docstring:\n            return {}\n\n        assert yaml is not None, \"`pyyaml` must be installed to use parse_docstring.\"\n\n        # We support having regular docstrings before the schema\n        # definition. Here we return just the schema part from\n        # the docstring.\n        docstring = docstring.split(\"---\")[-1]\n\n        parsed = yaml.safe_load(docstring)\n\n        if not isinstance(parsed, dict):\n            # A regular docstring (not yaml formatted) can return\n            # a simple string here, which wouldn't follow the schema.\n            return {}\n\n        return parsed\n\n    def OpenAPIResponse(self, request: Request) -> Response:\n        routes = request.app.routes\n        schema = self.get_schema(routes=routes)\n        return OpenAPIResponse(schema)\n\n\nclass SchemaGenerator(BaseSchemaGenerator):\n    def __init__(self, base_schema: dict) -> None:\n        self.base_schema = base_schema\n\n    def get_schema(self, routes: typing.List[BaseRoute]) -> dict:\n        schema = dict(self.base_schema)\n        schema.setdefault(\"paths\", {})\n        endpoints_info = self.get_endpoints(routes)\n\n        for endpoint in endpoints_info:\n\n            parsed = self.parse_docstring(endpoint.func)\n\n            if not parsed:\n                continue\n\n            if endpoint.path not in schema[\"paths\"]:\n                schema[\"paths\"][endpoint.path] = {}\n\n            schema[\"paths\"][endpoint.path][endpoint.http_method] = parsed\n\n        return schema\n", "path": "starlette/schemas.py"}]}