]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Allow hiding from OpenAPI (and Swagger UI) `Query`, `Cookie`, `Header`, and `Path...
authorMark <mark.lee.chu.yong@gmail.com>
Sun, 23 Jan 2022 15:54:59 +0000 (23:54 +0800)
committerGitHub <noreply@github.com>
Sun, 23 Jan 2022 15:54:59 +0000 (16:54 +0100)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
docs/en/docs/tutorial/query-params-str-validations.md
docs_src/query_params_str_validations/tutorial014.py [new file with mode: 0644]
docs_src/query_params_str_validations/tutorial014_py310.py [new file with mode: 0644]
fastapi/openapi/utils.py
fastapi/param_functions.py
fastapi/params.py
tests/test_param_include_in_schema.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py [new file with mode: 0644]

index fcac1a4e0e91d6af368f61b0aa45cab759674f97..ee62b97181028e6cfdab3cc78b6f9ec8d458ae6d 100644 (file)
@@ -387,6 +387,22 @@ The docs will show it like this:
 
 <img src="/img/tutorial/query-params-str-validations/image01.png">
 
+## Exclude from OpenAPI
+
+To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameter `include_in_schema` of `Query` to `False`:
+
+=== "Python 3.6 and above"
+
+    ```Python hl_lines="10"
+    {!> ../../../docs_src/query_params_str_validations/tutorial014.py!}
+    ```
+
+=== "Python 3.10 and above"
+
+    ```Python hl_lines="7"
+    {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!}
+    ```
+
 ## Recap
 
 You can declare additional validations and metadata for your parameters.
diff --git a/docs_src/query_params_str_validations/tutorial014.py b/docs_src/query_params_str_validations/tutorial014.py
new file mode 100644 (file)
index 0000000..fb50bc2
--- /dev/null
@@ -0,0 +1,15 @@
+from typing import Optional
+
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(
+    hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+    if hidden_query:
+        return {"hidden_query": hidden_query}
+    else:
+        return {"hidden_query": "Not found"}
diff --git a/docs_src/query_params_str_validations/tutorial014_py310.py b/docs_src/query_params_str_validations/tutorial014_py310.py
new file mode 100644 (file)
index 0000000..7ae39c7
--- /dev/null
@@ -0,0 +1,11 @@
+from fastapi import FastAPI, Query
+
+app = FastAPI()
+
+
+@app.get("/items/")
+async def read_items(hidden_query: str | None = Query(None, include_in_schema=False)):
+    if hidden_query:
+        return {"hidden_query": hidden_query}
+    else:
+        return {"hidden_query": "Not found"}
index 0e73e21bf88b9af7d7c44ab149795434038bca6f..aff76b15edfed0717181967aee38322aa4c83815 100644 (file)
@@ -92,6 +92,8 @@ def get_openapi_operation_parameters(
     for param in all_route_params:
         field_info = param.field_info
         field_info = cast(Param, field_info)
+        if not field_info.include_in_schema:
+            continue
         parameter = {
             "name": param.alias,
             "in": field_info.in_.value,
index ff65d7271281b423a47772bb9d8bffaf5b4bb3a9..a553a1461f8d7605d1acbf9b45ade7246b03b1cf 100644 (file)
@@ -20,6 +20,7 @@ def Path(  # noqa: N802
     example: Any = Undefined,
     examples: Optional[Dict[str, Any]] = None,
     deprecated: Optional[bool] = None,
+    include_in_schema: bool = True,
     **extra: Any,
 ) -> Any:
     return params.Path(
@@ -37,6 +38,7 @@ def Path(  # noqa: N802
         example=example,
         examples=examples,
         deprecated=deprecated,
+        include_in_schema=include_in_schema,
         **extra,
     )
 
@@ -57,6 +59,7 @@ def Query(  # noqa: N802
     example: Any = Undefined,
     examples: Optional[Dict[str, Any]] = None,
     deprecated: Optional[bool] = None,
+    include_in_schema: bool = True,
     **extra: Any,
 ) -> Any:
     return params.Query(
@@ -74,6 +77,7 @@ def Query(  # noqa: N802
         example=example,
         examples=examples,
         deprecated=deprecated,
+        include_in_schema=include_in_schema,
         **extra,
     )
 
@@ -95,6 +99,7 @@ def Header(  # noqa: N802
     example: Any = Undefined,
     examples: Optional[Dict[str, Any]] = None,
     deprecated: Optional[bool] = None,
+    include_in_schema: bool = True,
     **extra: Any,
 ) -> Any:
     return params.Header(
@@ -113,6 +118,7 @@ def Header(  # noqa: N802
         example=example,
         examples=examples,
         deprecated=deprecated,
+        include_in_schema=include_in_schema,
         **extra,
     )
 
@@ -133,6 +139,7 @@ def Cookie(  # noqa: N802
     example: Any = Undefined,
     examples: Optional[Dict[str, Any]] = None,
     deprecated: Optional[bool] = None,
+    include_in_schema: bool = True,
     **extra: Any,
 ) -> Any:
     return params.Cookie(
@@ -150,6 +157,7 @@ def Cookie(  # noqa: N802
         example=example,
         examples=examples,
         deprecated=deprecated,
+        include_in_schema=include_in_schema,
         **extra,
     )
 
index 3cab98b78dff15d7c32d47d1f5646f40149c82f9..042bbd42ff8b0a7cefd536c7fd43a3ae70ad28e3 100644 (file)
@@ -31,11 +31,13 @@ class Param(FieldInfo):
         example: Any = Undefined,
         examples: Optional[Dict[str, Any]] = None,
         deprecated: Optional[bool] = None,
+        include_in_schema: bool = True,
         **extra: Any,
     ):
         self.deprecated = deprecated
         self.example = example
         self.examples = examples
+        self.include_in_schema = include_in_schema
         super().__init__(
             default,
             alias=alias,
@@ -75,6 +77,7 @@ class Path(Param):
         example: Any = Undefined,
         examples: Optional[Dict[str, Any]] = None,
         deprecated: Optional[bool] = None,
+        include_in_schema: bool = True,
         **extra: Any,
     ):
         self.in_ = self.in_
@@ -93,6 +96,7 @@ class Path(Param):
             deprecated=deprecated,
             example=example,
             examples=examples,
+            include_in_schema=include_in_schema,
             **extra,
         )
 
@@ -117,6 +121,7 @@ class Query(Param):
         example: Any = Undefined,
         examples: Optional[Dict[str, Any]] = None,
         deprecated: Optional[bool] = None,
+        include_in_schema: bool = True,
         **extra: Any,
     ):
         super().__init__(
@@ -134,6 +139,7 @@ class Query(Param):
             deprecated=deprecated,
             example=example,
             examples=examples,
+            include_in_schema=include_in_schema,
             **extra,
         )
 
@@ -159,6 +165,7 @@ class Header(Param):
         example: Any = Undefined,
         examples: Optional[Dict[str, Any]] = None,
         deprecated: Optional[bool] = None,
+        include_in_schema: bool = True,
         **extra: Any,
     ):
         self.convert_underscores = convert_underscores
@@ -177,6 +184,7 @@ class Header(Param):
             deprecated=deprecated,
             example=example,
             examples=examples,
+            include_in_schema=include_in_schema,
             **extra,
         )
 
@@ -201,6 +209,7 @@ class Cookie(Param):
         example: Any = Undefined,
         examples: Optional[Dict[str, Any]] = None,
         deprecated: Optional[bool] = None,
+        include_in_schema: bool = True,
         **extra: Any,
     ):
         super().__init__(
@@ -218,6 +227,7 @@ class Cookie(Param):
             deprecated=deprecated,
             example=example,
             examples=examples,
+            include_in_schema=include_in_schema,
             **extra,
         )
 
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
new file mode 100644 (file)
index 0000000..4eaac72
--- /dev/null
@@ -0,0 +1,239 @@
+from typing import Optional
+
+import pytest
+from fastapi import Cookie, FastAPI, Header, Path, Query
+from fastapi.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/hidden_cookie")
+async def hidden_cookie(
+    hidden_cookie: Optional[str] = Cookie(None, include_in_schema=False)
+):
+    return {"hidden_cookie": hidden_cookie}
+
+
+@app.get("/hidden_header")
+async def hidden_header(
+    hidden_header: Optional[str] = Header(None, include_in_schema=False)
+):
+    return {"hidden_header": hidden_header}
+
+
+@app.get("/hidden_path/{hidden_path}")
+async def hidden_path(hidden_path: str = Path(..., include_in_schema=False)):
+    return {"hidden_path": hidden_path}
+
+
+@app.get("/hidden_query")
+async def hidden_query(
+    hidden_query: Optional[str] = Query(None, include_in_schema=False)
+):
+    return {"hidden_query": hidden_query}
+
+
+client = TestClient(app)
+
+openapi_shema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/hidden_cookie": {
+            "get": {
+                "summary": "Hidden Cookie",
+                "operationId": "hidden_cookie_hidden_cookie_get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+        "/hidden_header": {
+            "get": {
+                "summary": "Hidden Header",
+                "operationId": "hidden_header_hidden_header_get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+        "/hidden_path/{hidden_path}": {
+            "get": {
+                "summary": "Hidden Path",
+                "operationId": "hidden_path_hidden_path__hidden_path__get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+        "/hidden_query": {
+            "get": {
+                "summary": "Hidden Query",
+                "operationId": "hidden_query_hidden_query_get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_shema
+
+
+@pytest.mark.parametrize(
+    "path,cookies,expected_status,expected_response",
+    [
+        (
+            "/hidden_cookie",
+            {},
+            200,
+            {"hidden_cookie": None},
+        ),
+        (
+            "/hidden_cookie",
+            {"hidden_cookie": "somevalue"},
+            200,
+            {"hidden_cookie": "somevalue"},
+        ),
+    ],
+)
+def test_hidden_cookie(path, cookies, expected_status, expected_response):
+    response = client.get(path, cookies=cookies)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
+
+
+@pytest.mark.parametrize(
+    "path,headers,expected_status,expected_response",
+    [
+        (
+            "/hidden_header",
+            {},
+            200,
+            {"hidden_header": None},
+        ),
+        (
+            "/hidden_header",
+            {"Hidden-Header": "somevalue"},
+            200,
+            {"hidden_header": "somevalue"},
+        ),
+    ],
+)
+def test_hidden_header(path, headers, expected_status, expected_response):
+    response = client.get(path, headers=headers)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
+
+
+def test_hidden_path():
+    response = client.get("/hidden_path/hidden_path")
+    assert response.status_code == 200
+    assert response.json() == {"hidden_path": "hidden_path"}
+
+
+@pytest.mark.parametrize(
+    "path,expected_status,expected_response",
+    [
+        (
+            "/hidden_query",
+            200,
+            {"hidden_query": None},
+        ),
+        (
+            "/hidden_query?hidden_query=somevalue",
+            200,
+            {"hidden_query": "somevalue"},
+        ),
+    ],
+)
+def test_hidden_query(path, expected_status, expected_response):
+    response = client.get(path)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py
new file mode 100644 (file)
index 0000000..98ae5a6
--- /dev/null
@@ -0,0 +1,82 @@
+from fastapi.testclient import TestClient
+
+from docs_src.query_params_str_validations.tutorial014 import app
+
+client = TestClient(app)
+
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "get": {
+                "summary": "Read Items",
+                "operationId": "read_items_items__get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+def test_hidden_query():
+    response = client.get("/items?hidden_query=somevalue")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"hidden_query": "somevalue"}
+
+
+def test_no_hidden_query():
+    response = client.get("/items")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"hidden_query": "Not found"}
diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py
new file mode 100644 (file)
index 0000000..33f3d5f
--- /dev/null
@@ -0,0 +1,91 @@
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "get": {
+                "summary": "Read Items",
+                "operationId": "read_items_items__get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+        }
+    },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+    from docs_src.query_params_str_validations.tutorial014_py310 import app
+
+    client = TestClient(app)
+    return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_hidden_query(client: TestClient):
+    response = client.get("/items?hidden_query=somevalue")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"hidden_query": "somevalue"}
+
+
+@needs_py310
+def test_no_hidden_query(client: TestClient):
+    response = client.get("/items")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"hidden_query": "Not found"}