if value is not None:
params_to_process[get_validation_alias(field)] = value
processed_keys.add(alias or get_validation_alias(field))
+ # For headers with convert_underscores=True, mark both the converted
+ # header name and the original field alias as processed to avoid
+ # accepting the original alias as an extra header.
+ processed_keys.add(get_validation_alias(field))
for key in received_params.keys():
if key not in processed_keys:
model_config = {"extra": "allow"}
+class AuthHeaders(BaseModel):
+ x_user_id: str
+
+
@app.get("/query")
async def query_model_with_extra(data: Model = Query()):
return data
return data
+@app.get("/header-requires-hyphen")
+async def header_model_requires_hyphen(data: AuthHeaders = Header()):
+ return data
+
+
def test_query_pass_extra_list():
client = TestClient(app)
resp = client.get(
assert resp_json["param2"] == "456"
+def test_header_model_prefers_hyphenated_header_with_convert_underscores():
+ client = TestClient(app)
+
+ resp = client.get(
+ "/header-requires-hyphen",
+ headers=[
+ ("x-user-id", "hyphenated-value"),
+ ("x_user_id", "underscore-value"),
+ ],
+ )
+
+ assert resp.status_code == 200
+ assert resp.json() == {"x_user_id": "hyphenated-value"}
+
+
+def test_header_model_rejects_underscore_header_with_convert_underscores():
+ client = TestClient(app)
+
+ resp = client.get(
+ "/header-requires-hyphen", headers={"x_user_id": "underscore-value"}
+ )
+
+ assert resp.status_code == 422
+ assert resp.json()["detail"][0]["loc"] == ["header", "x_user_id"]
+
+
def test_cookie_pass_extra_list():
client = TestClient(app)
client.cookies = [