Getting Started with Python FastAPI — Concepts, Install & Running on PyCharm (feat. uvicorn)

After working only with Java Spring Boot, I had to build something in Python, so I wrote this starter note while setting up FastAPI for the first time. The goals of this post are:

  1. Why use FastAPI (with uvicorn)
  2. Python's GIL and FastAPI — direction for development (a taste)
  3. Installing FastAPI — FastAPI and the ASGI server (uvicorn), plus a primer on web applications
  4. Running FastAPI on PyCharm — virtualenv vs system environment, two ways to run uvicorn, and the built-in Swagger UI

1. Why FastAPI?

FastAPI is praised for high performance through async processing and concise code. It is a good fit when fast response time is critical — for example, a "data analytics platform."

That said, you need to distinguish CPU-bound tasks from I/O-bound tasks. FastAPI shines on I/O-bound work (DB calls, external API calls, etc.), but when there are many CPU-bound tasks, async alone gives limited gains because of Python's GIL.

📘 FastAPI official docs — high performance, easy to learn, fast to code, ready for production.

2. Python's GIL & FastAPI

Because of the GIL (Global Interpreter Lock), Python cannot achieve true parallelism even with multiple threads. The GIL allows only one thread to execute Python bytecode at a time (to prevent race conditions), so multithreading does not help CPU-bound tasks.

Therefore, FastAPI's async (via uvicorn) is advantageous for I/O-bound work, but if you have many CPU-bound tasks you need multiprocessing or optimization in another language.

The GIL is worth understanding well, because it changes your development direction and code structure entirely. Especially if you come from Java Spring Boot (MVC), understand the difference between Python's and Java's async models before you start — it saves a lot of later refactoring. (I'll cover the GIL in depth in a separate post.)

3. Installing FastAPI

If pip or your Python environment variables are not set up yet, read the post below first.

Installing Python on Windows + Setting the PATH — When 'python' Is Not Recognized
Install Python on Windows and register the install path and the Scripts (pip) path in the system PATH so that just typing 'python' or 'pip' works in cmd. Covers the difference between user and system variables and the site-packages structure of pip-installed libraries.
taystudios.com/blog

3.1 Install FastAPI

Use pip, Python's package manager, to install the fastapi package.

pip install fastapi

To confirm the install, check that fastapi exists under Lib\site-packages and Scripts of your Python install path (e.g., D:\Python).

3.2 Install the ASGI (Asynchronous Server Gateway Interface) server

pip install "uvicorn[standard]"

ASGI is an interface for async web applications. If WSGI is the standard for synchronous web applications, ASGI is the standard extended to support async ones.

  • ASGI — Uvicorn, Daphne / used with FastAPI
  • WSGI — Gunicorn, uWSGI, Waitress / used with Django, Flask, etc.

Abstract, docs-style definitions may not click at first. I'll do a deep dive into the structure and mechanics of ASGI, WSGI, and frameworks like FastAPI in a separate post.

3.3 (Reference) Concept mapping for Java developers (Tomcat, Netty)

By role, Spring Boot (MVC)'s Tomcat and Spring WebFlux's Netty are analogous to WSGI and ASGI servers respectively. Because the roles are similar you might call these server gateways a "WAS," but strictly speaking that is not accurate.

Application servers (processes) like Tomcat, Netty, WSGI, and ASGI all run an internal loop to receive and process incoming HTTP requests (including request parsing, etc.).

Developers then pick a framework like Spring Boot, Flask, or FastAPI to build the service logic on top of such a server. For reference, Java's Tomcat is written in Java, while uvicorn is known to be written in Cython.

3.4 (Important) Web framework vs web application — relationship and roles

You might reasonably wonder the following.

Do web frameworks like FastAPI or Spring fail to act as a server without a web application such as Uvicorn, Gunicorn, Tomcat, Netty, or Jetty?

Diagram of the relationship between a web framework and a web application — the framework runs on top of the application server Figure 1. A simple diagram of the web framework / web application relationship

My answer is: unless you write your own while-loop server, a web framework alone cannot be a server. A web framework is less a web server itself and more a tool that makes it easy to develop an application that runs on top of a web server (application). Low-level work like network communication and thread management is handled by the web application.

In short, a web application server manages client communication, manages threads to efficiently handle many concurrent requests, and provides an environment to run the application logic. In Python's case, uvicorn even handles parent-child process management.

"But Spring Boot or fastapi[standard] starts a server just by running it?"

That's because Spring Boot ships with an embedded Tomcat, and installing fastapi[standard] and running fastapi dev main.py runs the embedded uvicorn.

📘 Uvicorn official docs — an ASGI web server, for Python.

Here is how to run uvicorn manually (from the uvicorn docs).

import asyncio
import uvicorn

async def app(scope, receive, send):
    ...

async def main():
    config = uvicorn.Config("main:app", port=5000, log_level="info")
    server = uvicorn.Server(config)
    await server.serve()

if __name__ == "__main__":
    asyncio.run(main())

You can see "main:app" being passed to uvicorn and run asynchronously via asyncio.run(main()).

4. Running FastAPI with uvicorn in PyCharm

Now that we've covered the concepts, let's actually set up and run it in PyCharm (Community 2024.2.3). We installed fastapi and uvicorn[standard] into the global site-packages via steps 3.1 and 3.2.

Term — global vs local site-packages When you install Python on your system, there is a library folder inside the install directory. Every project using that interpreter references it in common, so it's called global site-packages (e.g., D:\Python\Python.3.11.8\Lib\site-packages). Alternatively, you can give each project an isolated environment whose packages live only inside the project folder — a virtual environment (venv) = local site-packages. This is useful when different projects need different library versions.

4.1 Create a project (virtualenv / system environment)

PyCharm new project window — choosing interpreter type and environment Figure 2. Creating a new project

When you create a project in PyCharm, you choose an interpreter type (project venv / conda / custom environment). Here we'll bring in the Python interpreter we installed and reference the global site-packages.

  • Interpreter type — choose Custom environment
  • Environment — "Generate new (venv)" means use my interpreter but only the libraries inside the virtual environment. To reference global site-packages, choose "Select existing."

Comparing external library paths of a venv project and a global-site-packages project Figure 3. (Top) virtualenv / (Bottom) referencing global site-packages

With a virtual environment, Lib and Scripts folders are created inside the project, and external libraries reference that venv's Scripts. A project using global site-packages references the Python install path instead. If you want fastapi inside a venv, run pip install fastapi again from that environment's terminal.

4.2 Create main.py — the FastAPI instance

PyCharm recognizing the fastapi import Figure 4. fastapi is now recognized

Create main.py under the project and write the official FastAPI example.

# Source: FastAPI official docs
from typing import Union
from fastapi import FastAPI

app = FastAPI()


@app.get("/sync/say-hello")
def read_root():
    return {"Hello": "World"}

@app.get("/async/say-hello")
async def async_read_root():
    return {"async Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

app = FastAPI() creates the FastAPI instance, and you pass this app to uvicorn to run the web application.

4.3 Two ways to run with uvicorn (ASGI)

① Run uvicorn directly from the terminal

uvicorn main:app --reload

This means "run uvicorn and pass it the app object inside main.py." --reload restarts the server automatically on code changes — a development convenience that is not recommended in production. (You can set the port with --port and the number of worker processes with --workers; I'll cover workers in the GIL/async post.)

Running uvicorn — the reloader and the server run as separate processes Figure 5. uvicorn running

When uvicorn runs, the reloader and the uvicorn server run under separate process IDs. You can see that the reloader is its own process.

② Embed uvicorn in code (more intuitive)

from typing import Union
from fastapi import FastAPI
import asyncio
import uvicorn

app = FastAPI()


@app.get("/sync/say-hello")
def read_root():
    return {"Hello": "World"}

@app.get("/async/say-hello")
async def async_read_root():
    return {"async Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}


async def main():
    config = uvicorn.Config("main:app", port=8000, log_level="info")
    server = uvicorn.Server(config)
    await server.serve()

if __name__ == "__main__":
    asyncio.run(main())

Here we pass app to uvicorn.Config to run the server directly, and run main asynchronously via asyncio. The structure is intuitive and easier to follow.

Running main.py via Run in PyCharm Figure 6. Running main in PyCharm

You can run it from the terminal with python main.py or via PyCharm's Run.

Result of running embedded uvicorn — a single process ID Figure 7. Run result

The result is the same as running uvicorn from the terminal. Since we didn't spawn a separate --reload process, only the single uvicorn application process ID appears.

4.4 Sending a request

Confirming the /sync/say-hello GET response in a browser Figure 8. Sending a request

To check that the server works, send a GET request to http://127.0.0.1:8000/sync/say-hello; you can confirm it returns a proper response.

4.5 FastAPI's built-in Swagger UI

FastAPI's built-in Swagger UI (/docs) screen Figure 9. FastAPI's built-in Swagger UI

Unlike Java Spring Boot, FastAPI bundles Swagger UI rather than pulling it in as a dependency. If your server domain is http://127.0.0.1:8000, append /docs to the path to see the Swagger UI for the provided APIs.

Wrap-up

In this post we looked at FastAPI's advantages and installation, the relationship between a web framework and a web application, and ran a FastAPI server step by step in PyCharm.

FastAPI is a powerful web framework that delivers high performance through async processing, well suited to applications that need fast responses. I also emphasized that understanding Python's GIL matters.

Next, I plan to cover configuring FastAPI as an API server (DB setup, routers, service layer) and how uvicorn interacts with the OS internally — including managing multiprocessing via parent-child processes to work around GIL constraints.

References

  1. Uvicorn official docs
  2. FastAPI official docs
  3. What is the GIL — Python Wiki

📦 Migrated from my own Korean blog (my own writing). Original: taehyuklee.tistory.com/23

Share𝕏f

Comments