Repository: tiangolo/fastapi

Issue:
I need a way to specify servers in the openapi spec
### Is your feature request related to a problem

I want to be able to use the generated openapi.json doc as it is and hook it up with a document publishing flow, but i'm not able to because I have to add in information about `servers` manually.

### The solution you would like

Someway to specify at a global level what the base server url should be.

### Describe alternatives you've considered

Currently I'm doing this manually in the generated openapi.json by adding in something like - 
```
    "servers": [
        {
            "url": "http://example.com"
        }
    ]
```

I don't mind submitting a PR which enables this if someone can guide me about the changes that need to be made. One thing I saw was that the `get_openapi` method in `fastapi.openapi.utils`, doesn't expose a parameter for setting a value for the `servers` key.


Discussion:
It's @tiangolo's decision to make, but given this *is* part of the OpenAPI spec, I personally would be in favor of adding this as a keyword argument to `FastAPI`, and as an argument to `get_openapi`, making it easier to set this.

I think those should be the only changes you need to make (just make sure the value also gets passed to the `get_openapi` call, and added to the returned value in the `get_openapi` call).

It should be a quick PR if you want to open it.

I think eventually we should group the arguments to `FastAPI` into more nested chunks to make it a little easier to parse, but I would be fine with the approach described above for now. 
Hey there it's needed also here

Resolving patch:
diff --git a/fastapi/applications.py b/fastapi/applications.py
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -38,6 +38,7 @@ def __init__(
         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 @@ def __init__(
         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 @@ def openapi(self, openapi_prefix: str = "") -> Dict:
                 routes=self.routes,
                 openapi_prefix=openapi_prefix,
                 tags=self.openapi_tags,
+                servers=self.servers,
             )
         return self.openapi_schema
 
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -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
 
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -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)


Test patch:
diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py
new file mode 100644
--- /dev/null
+++ b/tests/test_openapi_servers.py
@@ -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