Introduction
When you try to start your FastAPI server, you’ve probably used uvicorn
pointing to the FastAPI app
:
1
uvicorn main:app --reload
While it works fine, it has some rooms for improvement.
First, when you restructure your server, you must enter the adjusted path to app
every time you start the server.
Suppose you put FastAPI app in src.main
then you would have to type. Even if it looked trivial, I hated typing it every time I start the server.
1
uvicorn src.main:app --reload
Also, with the typical approach, since the entry point of starting the server is app
, it’s hard to perform preliminary setups cleanly.
To make our server more modularized and easier to maintain, let’s make the server as a package.
Treat FastAPI Server as a Package
Instead of typing the command in a shell, we can programmatically start the FastAPI server with uvicorn by passing the FastAPI app path as a parameter.
1
uvicorn.run(app="src.main:app", host="127.0.0.1", port=8080)
With this in mind, let’s create a new project called dummy
.
1
2
3
4
5
6
.
├── dummy
│ ├── __main__.py
│ └── server.py
├── poetry.lock
└── pyproject.toml
All the server application codes would go inside dummy/
directory.
You notice that we have __main__.py
right under dummy/
. This file is a special module that enables you to treat a Python package as a executable script. So you would be able to “run” the Python package from the command line:
1
python -m dummy
Then, it would look at __main__.py
and execute the code in that package.
Hence, we must put the code to start the FastAPI app inside __main__.py
.
Now, let’s look at the code.
Implementation
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from fastapi import FastAPI
def create_app() -> FastAPI:
app = FastAPI(
title="dummy server",
version="0.1.0",
)
@app.get("/")
def health_check():
return {"message": "I'm doing fine. Thanks for asking."}
return app
app = create_app()
__main__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import argparse
import os
import uvicorn
def extract_args() -> dict:
argParser = argparse.ArgumentParser()
argParser.add_argument(
"-e",
"--env",
type=str,
help='Server environment. Either "local", "dev", or "prod".',
choices=["dev", "local", "prod"],
default="local",
)
args = argParser.parse_args()
return {"env": args.env}
def main(env: str) -> None:
print(f"- Running server in {env} environment...")
uvicorn.run(
app="dummy.server:app",
host="127.0.0.1",
port=8080,
reload=True if env == "local" else False,
)
if __name__ == "__main__":
args = extract_args()
main(env=args["env"])
In server.py
, we instantiate a FastAPI app. Then, in __main__.py
, we call uvicorn.run()
method to start the server, passing the FastAPI app path as a parameter.
1
uvicorn.run(app="dummy.server:app")
We can actually pass a FastAPI app instance as a parameter.
Another advantage is that we can pass any arguments when we start the server. In this example, the dummy
package accepts --env
argument to specify the development environment: local
, dev
, or prod
.
Now, let’s run the server with
1
python -m dummy --env local
When we go to localhost:8080
we can see that the server is successfully running.
We’ve looked at how to treat our FastAPI server as a package. It helps us to keep our code clean and also perform preliminary steps before running the server.