]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Add support for OpenAPI servers metadata (#1547)
authormikaello <2505178+mikaello@users.noreply.github.com>
Sun, 14 Jun 2020 13:38:29 +0000 (15:38 +0200)
committerGitHub <noreply@github.com>
Sun, 14 Jun 2020 13:38:29 +0000 (15:38 +0200)
* feat: add servers option for OpenAPI

Closes #872

* ✨ Use dicts for OpenAPI servers

* ♻️ Update OpenAPI Server model to support relative URLs

* ✅ Add tests for OpenAPI servers

* ♻️ Re-order parameter location of servers for OpenAPI

* 🎨 Format code

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
fastapi/applications.py
fastapi/openapi/models.py
fastapi/openapi/utils.py
tests/test_openapi_servers.py [new file with mode: 0644]

index 3306aab3d95eb5de68b8a3733563b6289781947e..c21087911ebf4deb9e1da0594e94f1b4527574ab 100644 (file)
@@ -38,6 +38,7 @@ class FastAPI(Starlette):
         version: str = "0.1.0",
         openapi_url: Optional[str] = "/openapi.json",
         openapi_tags: Optional[List[Dict[str, Any]]] = None,
+        servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
         default_response_class: Type[Response] = JSONResponse,
         docs_url: Optional[str] = "/docs",
         redoc_url: Optional[str] = "/redoc",
@@ -70,6 +71,7 @@ class FastAPI(Starlette):
         self.title = title
         self.description = description
         self.version = version
+        self.servers = servers
         self.openapi_url = openapi_url
         self.openapi_tags = openapi_tags
         # TODO: remove when discarding the openapi_prefix parameter
@@ -106,6 +108,7 @@ class FastAPI(Starlette):
                 routes=self.routes,
                 openapi_prefix=openapi_prefix,
                 tags=self.openapi_tags,
+                servers=self.servers,
             )
         return self.openapi_schema
 
index a7c4460fab43a100d0fd0966d36b7b716eb521fc..13dc59f18952706c2e29a6e127a2882849898844 100644 (file)
@@ -63,7 +63,7 @@ class ServerVariable(BaseModel):
 
 
 class Server(BaseModel):
-    url: AnyUrl
+    url: Union[AnyUrl, str]
     description: Optional[str] = None
     variables: Optional[Dict[str, ServerVariable]] = None
 
index b6221ca202826643a500be1b88581e3ffe3e5943..5a0c89a894cb30dd31c1bc52435ba59a67c164eb 100644 (file)
@@ -86,7 +86,7 @@ def get_openapi_security_definitions(flat_dependant: Dependant) -> Tuple[Dict, L
 def get_openapi_operation_parameters(
     *,
     all_route_params: Sequence[ModelField],
-    model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str]
+    model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
 ) -> List[Dict[str, Any]]:
     parameters = []
     for param in all_route_params:
@@ -112,7 +112,7 @@ def get_openapi_operation_parameters(
 def get_openapi_operation_request_body(
     *,
     body_field: Optional[ModelField],
-    model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str]
+    model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
 ) -> Optional[Dict]:
     if not body_field:
         return None
@@ -318,12 +318,15 @@ def get_openapi(
     description: str = None,
     routes: Sequence[BaseRoute],
     openapi_prefix: str = "",
-    tags: Optional[List[Dict[str, Any]]] = None
+    tags: Optional[List[Dict[str, Any]]] = None,
+    servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
 ) -> Dict:
     info = {"title": title, "version": version}
     if description:
         info["description"] = description
     output: Dict[str, Any] = {"openapi": openapi_version, "info": info}
+    if servers:
+        output["servers"] = servers
     components: Dict[str, Dict] = {}
     paths: Dict[str, Dict] = {}
     flat_models = get_flat_models_from_routes(routes)
diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py
new file mode 100644 (file)
index 0000000..a210154
--- /dev/null
@@ -0,0 +1,60 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+app = FastAPI(
+    servers=[
+        {"url": "/", "description": "Default, relative server"},
+        {
+            "url": "http://staging.localhost.tiangolo.com:8000",
+            "description": "Staging but actually localhost still",
+        },
+        {"url": "https://prod.example.com"},
+    ]
+)
+
+
+@app.get("/foo")
+def foo():
+    return {"message": "Hello World"}
+
+
+client = TestClient(app)
+
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "servers": [
+        {"url": "/", "description": "Default, relative server"},
+        {
+            "url": "http://staging.localhost.tiangolo.com:8000",
+            "description": "Staging but actually localhost still",
+        },
+        {"url": "https://prod.example.com"},
+    ],
+    "paths": {
+        "/foo": {
+            "get": {
+                "summary": "Foo",
+                "operationId": "foo_foo_get",
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+            }
+        }
+    },
+}
+
+
+def test_openapi_servers():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == openapi_schema
+
+
+def test_app():
+    response = client.get("/foo")
+    assert response.status_code == 200, response.text