Skip to content

Commit 795cf60

Browse files
Kludextomchristie
andauthored
Remove converter from path when generating OpenAPI schema (#1648)
* Remove converter from path when generating `OpenAPI` schema * Update starlette/schemas.py Co-authored-by: Tom Christie <[email protected]> Co-authored-by: Tom Christie <[email protected]>
1 parent 92c1f1e commit 795cf60

File tree

2 files changed

+45
-5
lines changed

2 files changed

+45
-5
lines changed

starlette/schemas.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import inspect
2+
import re
23
import typing
34

45
from starlette.requests import Request
@@ -49,10 +50,11 @@ def get_endpoints(
4950

5051
for route in routes:
5152
if isinstance(route, Mount):
53+
path = self._remove_converter(route.path)
5254
routes = route.routes or []
5355
sub_endpoints = [
5456
EndpointInfo(
55-
path="".join((route.path, sub_endpoint.path)),
57+
path="".join((path, sub_endpoint.path)),
5658
http_method=sub_endpoint.http_method,
5759
func=sub_endpoint.func,
5860
)
@@ -64,23 +66,32 @@ def get_endpoints(
6466
continue
6567

6668
elif inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint):
69+
path = self._remove_converter(route.path)
6770
for method in route.methods or ["GET"]:
6871
if method == "HEAD":
6972
continue
7073
endpoints_info.append(
71-
EndpointInfo(route.path, method.lower(), route.endpoint)
74+
EndpointInfo(path, method.lower(), route.endpoint)
7275
)
7376
else:
77+
path = self._remove_converter(route.path)
7478
for method in ["get", "post", "put", "patch", "delete", "options"]:
7579
if not hasattr(route.endpoint, method):
7680
continue
7781
func = getattr(route.endpoint, method)
78-
endpoints_info.append(
79-
EndpointInfo(route.path, method.lower(), func)
80-
)
82+
endpoints_info.append(EndpointInfo(path, method.lower(), func))
8183

8284
return endpoints_info
8385

86+
def _remove_converter(self, path: str) -> str:
87+
"""
88+
Remove the converter from the path.
89+
For example, a route like this:
90+
Route("/users/{id:int}", endpoint=get_user, methods=["GET"])
91+
Should be represented as `/users/{id}` in the OpenAPI schema.
92+
"""
93+
return re.sub(r":\w+}", "}", path)
94+
8495
def parse_docstring(self, func_or_method: typing.Callable) -> dict:
8596
"""
8697
Given a function, parse the docstring as YAML and return a dictionary of info.

tests/test_schemas.py

+29
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ def ws(session):
1313
pass # pragma: no cover
1414

1515

16+
def get_user(request):
17+
"""
18+
responses:
19+
200:
20+
description: A user.
21+
examples:
22+
{"username": "tom"}
23+
"""
24+
pass # pragma: no cover
25+
26+
1627
def list_users(request):
1728
"""
1829
responses:
@@ -103,6 +114,7 @@ def schema(request):
103114
app = Starlette(
104115
routes=[
105116
WebSocketRoute("/ws", endpoint=ws),
117+
Route("/users/{id:int}", endpoint=get_user, methods=["GET"]),
106118
Route("/users", endpoint=list_users, methods=["GET", "HEAD"]),
107119
Route("/users", endpoint=create_user, methods=["POST"]),
108120
Route("/orgs", endpoint=OrganisationsEndpoint),
@@ -168,6 +180,16 @@ def test_schema_generation():
168180
}
169181
},
170182
},
183+
"/users/{id}": {
184+
"get": {
185+
"responses": {
186+
200: {
187+
"description": "A user.",
188+
"examples": {"username": "tom"},
189+
}
190+
}
191+
},
192+
},
171193
},
172194
}
173195

@@ -216,6 +238,13 @@ def test_schema_generation():
216238
description: A user.
217239
examples:
218240
username: tom
241+
/users/{id}:
242+
get:
243+
responses:
244+
200:
245+
description: A user.
246+
examples:
247+
username: tom
219248
"""
220249

221250

0 commit comments

Comments
 (0)