Skip to content

Commit 2863363

Browse files
committed
fix: route function parameter aliasing
1 parent db705fa commit 2863363

File tree

9 files changed

+313
-80
lines changed

9 files changed

+313
-80
lines changed

ellar/common/params/args/request_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def build_body_field(self) -> None:
7272
_fields_required, _body_param_class = [], {}
7373
for f in _body_resolvers_model_fields:
7474
f.field_info.embed = True # type:ignore[attr-defined]
75-
body_model_field.__fields__[f.alias or f.name] = f
75+
body_model_field.__fields__[f.name] = f
7676
_fields_required.append(f.required)
7777
_body_param_class[
7878
getattr(f.field_info, "media_type", "application/json")

tests/test_routing/test_body_union_schema.py

Lines changed: 127 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ellar.openapi import OpenAPIDocumentBuilder
88
from ellar.testing import Test
99

10-
from .sample import Item, OtherItem
10+
from .sample import Item, OtherItem, Product
1111

1212
tm = Test.create_test_module()
1313

@@ -24,8 +24,15 @@ def embed_qty(qty: Body[int, Body.P(12, embed=True)]):
2424
return {"qty": qty}
2525

2626

27+
@post("/items/alias")
28+
def alias_qty(qty: Body[int, Body.P(12, embed=True, alias="aliasQty")]):
29+
return {"qty": qty}
30+
31+
2732
app = tm.create_application()
2833
app.router.append(save_union_body_and_embedded_body)
34+
app.router.append(embed_qty)
35+
app.router.append(alias_qty)
2936

3037
client = tm.get_test_client()
3138

@@ -68,7 +75,75 @@ def embed_qty(qty: Body[int, Body.P(12, embed=True)]):
6875
},
6976
},
7077
}
71-
}
78+
},
79+
"/items/alias": {
80+
"post": {
81+
"operationId": "alias_qty_items_alias_post",
82+
"requestBody": {
83+
"content": {
84+
"application/json": {
85+
"schema": {
86+
"$ref": "#/components/schemas/body_alias_qty_items_alias_post"
87+
}
88+
}
89+
}
90+
},
91+
"responses": {
92+
"200": {
93+
"description": "Successful Response",
94+
"content": {
95+
"application/json": {
96+
"schema": {"title": "Response Model", "type": "object"}
97+
}
98+
},
99+
},
100+
"422": {
101+
"description": "Validation Error",
102+
"content": {
103+
"application/json": {
104+
"schema": {
105+
"$ref": "#/components/schemas/HTTPValidationError"
106+
}
107+
}
108+
},
109+
},
110+
},
111+
}
112+
},
113+
"/items/embed": {
114+
"post": {
115+
"operationId": "embed_qty_items_embed_post",
116+
"requestBody": {
117+
"content": {
118+
"application/json": {
119+
"schema": {
120+
"$ref": "#/components/schemas/body_embed_qty_items_embed_post"
121+
}
122+
}
123+
}
124+
},
125+
"responses": {
126+
"200": {
127+
"description": "Successful Response",
128+
"content": {
129+
"application/json": {
130+
"schema": {"title": "Response Model", "type": "object"}
131+
}
132+
},
133+
},
134+
"422": {
135+
"description": "Validation Error",
136+
"content": {
137+
"application/json": {
138+
"schema": {
139+
"$ref": "#/components/schemas/HTTPValidationError"
140+
}
141+
}
142+
},
143+
},
144+
},
145+
}
146+
},
72147
},
73148
"components": {
74149
"schemas": {
@@ -109,6 +184,30 @@ def embed_qty(qty: Body[int, Body.P(12, embed=True)]):
109184
"type": {"title": "Error Type", "type": "string"},
110185
},
111186
},
187+
"body_alias_qty_items_alias_post": {
188+
"title": "body_alias_qty_items_alias_post",
189+
"type": "object",
190+
"properties": {
191+
"aliasQty": {
192+
"title": "Aliasqty",
193+
"type": "integer",
194+
"default": 12,
195+
"include_in_schema": True,
196+
}
197+
},
198+
},
199+
"body_embed_qty_items_embed_post": {
200+
"title": "body_embed_qty_items_embed_post",
201+
"type": "object",
202+
"properties": {
203+
"qty": {
204+
"title": "Qty",
205+
"type": "integer",
206+
"default": 12,
207+
"include_in_schema": True,
208+
}
209+
},
210+
},
112211
"body_save_union_body_and_embedded_body_items__post": {
113212
"title": "body_save_union_body_and_embedded_body_items__post",
114213
"required": ["item"],
@@ -154,14 +253,36 @@ def test_post_item():
154253

155254

156255
def test_embed_body():
256+
response = client.post("/items/embed", json={"qty": 232})
257+
assert response.status_code == 200, response.text
258+
assert response.json() == {"qty": 232}
259+
260+
261+
def test_embed_and_alias_body():
262+
response = client.post("/items/alias", json={"aliasQty": 232})
263+
assert response.status_code == 200, response.text
264+
assert response.json() == {"qty": 232}
265+
266+
267+
def test_alias_with_more_body():
268+
@post("/product")
269+
async def create_item(
270+
product: "Product" = Body(alias="item"), qty: int = Body(alias="aliasQty")
271+
): # just to test get_typed_annotation in ellar.common.params.args.base
272+
return {"item": product, "aliasQty": qty}
273+
157274
_tm = Test.create_test_module()
158275
_app = _tm.create_application()
159-
_app.router.append(embed_qty)
276+
_app.router.append(create_item)
160277

161278
_client = _tm.get_test_client()
162-
response = _client.post("/items/embed", json={"qty": 232})
163-
assert response.status_code == 200, response.text
164-
assert response.json() == {"qty": 232}
279+
280+
body = {
281+
"item": {"name": "Foo", "description": "Some description", "price": 5.5},
282+
"aliasQty": 234,
283+
}
284+
response = _client.post("/product", json=body)
285+
assert response.json() == body
165286

166287

167288
@patch.object(Request, "body")

tests/test_routing/test_cookie_schema.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,51 @@ def cookie_params_mixed_schema(
2828
return {"filters": filters.dict(), "data": data.dict()}
2929

3030

31+
@router.post("/cookie-alias")
32+
def cookie_with_alias(
33+
request: Inject[Request],
34+
qty: Cookie[str, Cookie.P(alias="aliasQty")],
35+
):
36+
return {"aliasQty": qty}
37+
38+
3139
tm = Test.create_test_module(routers=(router,))
3240
app = tm.create_application()
3341

3442

43+
def test_cookie_with_alias():
44+
client = tm.get_test_client()
45+
response = client.post(
46+
"/cookie-alias",
47+
json={},
48+
cookies={
49+
"aliasQty": "234",
50+
},
51+
)
52+
53+
assert response.json() == {
54+
"aliasQty": "234",
55+
}
56+
57+
response = client.post(
58+
"/cookie-alias",
59+
cookies={
60+
"qty": "234",
61+
},
62+
)
63+
assert response.status_code == 422
64+
json = response.json()
65+
assert json == {
66+
"detail": [
67+
{
68+
"loc": ["cookie", "aliasQty"],
69+
"msg": "none is not an allowed value",
70+
"type": "type_error.none.not_allowed",
71+
}
72+
]
73+
}
74+
75+
3576
def test_cookie_request():
3677
client = tm.get_test_client()
3778
response = client.get(

tests/test_routing/test_form_schema.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ def form_params_schema(
1919
return filters.dict()
2020

2121

22+
@mr.post("/form-alias")
23+
def form_with_alias(
24+
request: Inject[Request],
25+
qty: int = Form(..., alias="aliasQty"),
26+
):
27+
return {"aliasQty": qty}
28+
29+
2230
test_module = Test.create_test_module(routers=(mr,))
2331
app = test_module.create_application()
2432

@@ -49,6 +57,37 @@ def test_request():
4957
}
5058

5159

60+
def test_form_with_alias():
61+
client = test_module.get_test_client()
62+
response = client.post(
63+
"/form-alias",
64+
data={
65+
"aliasQty": 234,
66+
},
67+
)
68+
assert response.json() == {
69+
"aliasQty": 234,
70+
}
71+
72+
response = client.post(
73+
"/form-alias",
74+
data={
75+
"qty": 234,
76+
},
77+
)
78+
assert response.status_code == 422
79+
json = response.json()
80+
assert json == {
81+
"detail": [
82+
{
83+
"loc": ["body", "aliasQty"],
84+
"msg": "value is not a valid integer",
85+
"type": "type_error.integer",
86+
}
87+
]
88+
}
89+
90+
5291
def test_schema():
5392
document = serialize_object(OpenAPIDocumentBuilder().build_document(app))
5493
params = document["paths"]["/form-schema"]["post"]["requestBody"]

tests/test_routing/test_formparsers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def form_upload_single_case_1(test: UploadFile = File()):
3333

3434
@router.post("/mixed")
3535
async def form_upload_single_case_2(
36-
test1: UploadFile = File(), test2: UploadFile = File()
36+
test1: UploadFile = File(alias="test_alias"), test2: UploadFile = File()
3737
):
3838
content1 = await test1.read()
3939
content2 = await test2.read()
@@ -143,7 +143,7 @@ def test_multipart_request_multiple_files(tmpdir):
143143
client = tm.get_test_client()
144144
with open(path1, "rb") as f1, open(path2, "rb") as f2:
145145
response = client.post(
146-
"/mixed", files={"test1": f1, "test2": ("test2.txt", f2, "text/plain")}
146+
"/mixed", files={"test_alias": f1, "test2": ("test2.txt", f2, "text/plain")}
147147
)
148148
assert response.json() == {
149149
"test1": {

tests/test_routing/test_forms.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)