Foundation Setup for Fast APIs - Python Type Hints

What are Type Hints?

Type hints let you tell Python what type of data a variable or function expects.


    # Without type hints
    def add(a, b):
        return a + b

    # With type hints
    def add(a: int, b: int) -> int:
        return a + b

The : int after each parameter says "this should be an integer". The -> int says "this function returns an integer".


Important Thing to Understand

Type hints in Python are not enforced at runtime. Python won't throw an error if you pass a string where you said int. They are hints — for you, your editor, and tools like FastAPI.


    def add(a: int, b: int) -> int:
        return a + b

    add(5, 3)        # correct usage
    add("hi", "yo")  # Python won't crash — but your editor will warn you

FastAPI however does enforce them — it uses type hints to validate incoming data automatically. That's one of its superpowers.


Variable Type Hints


    name: str = "Gagan"
    age: int = 22
    height: float = 5.11
    is_student: bool = True

You'll rarely type hint simple variables. More common in function parameters and return types.


Function Type Hints


    def greet(name: str) -> str:
        return f"Hello, {name}!"

    def calculate_area(length: float, width: float) -> float:
        return length * width

    def is_adult(age: int) -> bool:
        return age >= 18

    def print_info(name: str, age: int) -> None:    # None means returns nothing
        print(f"{name} is {age} years old")


Type Hints for Collections


    from typing import List, Dict, Tuple, Set

    # List of strings
    def greet_all(names: List[str]) -> None:
        for name in names:
            print(f"Hello {name}")

    # Dictionary
    def get_user_info(user: Dict[str, str]) -> str:
        return user["name"]

    # List of integers
    def get_average(marks: List[int]) -> float:
        return sum(marks) / len(marks)

In Python 3.9+ you can use lowercase directly:


    # Modern way — Python 3.9+
    def greet_all(names: list[str]) -> None:
        for name in names:
            print(f"Hello {name}")

    def get_scores(data: dict[str, int]) -> list[int]:
        return list(data.values())

Since you're using Python 3.13 — always use the modern lowercase style.


Optional — Value That Might Be None

Very common in APIs — some fields are optional:


    from typing import Optional

    # Old way
    def greet(name: str, title: Optional[str] = None) -> str:
        if title:
            return f"Hello, {title} {name}!"
        return f"Hello, {name}!"

    # Modern way — Python 3.10+
    def greet(name: str, title: str | None = None) -> str:
        if title:
            return f"Hello, {title} {name}!"
        return f"Hello, {name}!"

    print(greet("Gagan"))              # Hello, Gagan!
    print(greet("Gagan", "Mr."))       # Hello, Mr. Gagan!

str | None means "either a string or None". This is the modern Python 3.10+ syntax. Use this.


Union — Multiple Possible Types


    # Old way
    from typing import Union
    def process(value: Union[int, str]) -> str:
        return str(value)

    # Modern way — Python 3.10+
    def process(value: int | str) -> str:
        return str(value)


Type Hints with Classes


    class Student:
        def __init__(self, name: str, age: int, marks: list[int]) -> None:
            self.name = name
            self.age = age
            self.marks = marks

        def get_average(self) -> float:
            return sum(self.marks) / len(self.marks)

        def is_passing(self) -> bool:
            return self.get_average() >= 50

        def __str__(self) -> str:
            return f"Student({self.name}, avg={self.get_average():.1f})"


    def print_student(student: Student) -> None:
        print(student)


    s = Student("Gagan", 22, [85, 90, 78])
    print_student(s)


Quick Reference — Type Hints Cheat Sheet

# Basic types
name: str
age: int
price: float
active: bool

# Collections
items: list[str]
scores: dict[str, int]
coordinates: tuple[float, float]
unique_ids: set[int]

# Optional (might be None)
nickname: str | None = None

# Union (multiple types)
value: int | str

# Function return types
def get_name() -> str: ...
def process() -> None: ...
def get_data() -> list[dict[str, int]]: ...

Exercise — Rewrite with Type Hints

Take this existing code and add proper type hints:


    def calculate_bill(items, tax_rate):
        subtotal = sum(items)
        tax = subtotal * tax_rate
        total = subtotal + tax
        return total

    def get_student_info(name, age, marks):
        average = sum(marks) / len(marks)
        return {"name": name, "age": age, "average": average}

    def find_passing_students(students):
        return [s for s in students if s["average"] >= 50]

Expected answer:


    def calculate_bill(items: list[float], tax_rate: float) -> float:
        subtotal = sum(items)
        tax = subtotal * tax_rate
        total = subtotal + tax
        return total

    def get_student_info(name: str, age: int, marks: list[int]) -> dict[str, int | float | str]:
        average = sum(marks) / len(marks)
        return {"name": name, "age": age, "average": average}

    def find_passing_students(students: list[dict]) -> list[dict]:
        return [s for s in students if s["average"] >= 50]


Step 2: Virtual Environments

What is a Virtual Environment?

Every Python project needs different libraries and versions. Without virtual environments, all libraries install globally — projects start conflicting with each other.

Think of it like this:

Project A needs requests version 2.28
Project B needs requests version 2.31

Without venv — only one version installed globally — one project breaks
With venv — each project has its own isolated Python environment

Virtual environment = isolated Python installation for each project.

This is standard practice — every professional Python project uses one.


Creating a Virtual Environment

Open terminal in your project folder:

# Create virtual environment named 'venv'
python -m venv venv

This creates a venv folder in your project directory containing an isolated Python installation.

On Mac/Linux:

python3 -m venv venv

Activating the Virtual Environment

Windows:

venv\Scripts\activate

Mac/Linux:

source venv/bin/activate

After activation your terminal prompt changes:

(venv) C:\Users\Gagan\my-project>

The (venv) prefix tells you the virtual environment is active. Now any pip install goes into this project only — not globally.


Deactivating

deactivate

Prompt goes back to normal. You're back to global Python.


.gitignore — Never Commit venv

The venv folder is huge (thousands of files). Never commit it to git. Add it to .gitignore:

venv/
__pycache__/
*.pyc
.env

Instead commit requirements.txt so anyone can recreate the environment:

# Save all installed packages
pip freeze > requirements.txt

# Anyone else installs everything with
pip install -r requirements.txt

VS Code — Select Virtual Environment

After creating venv, tell VS Code to use it:

  1. Press Ctrl + Shift + P
  2. Type "Python: Select Interpreter"
  3. Choose the one that shows venv in the path

VS Code will now use your virtual environment automatically.


Step 3: Installing FastAPI

Now let's set up a real FastAPI project from scratch.


Create Project Folder

mkdir fastapi-learning
cd fastapi-learning

Create and Activate Virtual Environment

python -m venv venv

# Windows
venv\Scripts\activate

# Mac/Linux
source venv/bin/activate

Install FastAPI and Uvicorn

pip install fastapi uvicorn

FastAPI — the framework itself. Uvicorn — the server that runs your FastAPI app. Think of it like Nodemon in Node.js world.

Wait for installation to complete. Then verify:

pip show fastapi
pip show uvicorn

You should see version info for both.


Your First FastAPI App

Create a file called main.py inside your project folder:


    from fastapi import FastAPI

    app = FastAPI()


    @app.get("/")
    def read_root():
        return {"message": "Hello, World!"}


    @app.get("/hello/{name}")
    def say_hello(name: str):
        return {"message": f"Hello, {name}!"}

That's it. A complete running API in 8 lines.


Running the Server

uvicorn main:app --reload

Breaking this down:

  • main — your filename (main.py)
  • app — the FastAPI instance variable name
  • --reload — auto restart when you save changes (like nodemon)

You'll see:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process
INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Your API is running at http://localhost:8000


Testing Your First API

Open your browser and visit:

http://localhost:8000

{"message": "Hello, World!"}

http://localhost:8000/hello/Gagan

{"message": "Hello, Gagan!"}

It works. You just built and ran your first API with FastAPI.


Free Automatic Documentation — The Magic

This is one of FastAPI's biggest features. Visit:

http://localhost:8000/docs

You'll see a beautiful interactive API documentation page — Swagger UI — automatically generated from your code. No extra work.

You can:

  • See all your routes
  • Test them directly from the browser
  • See request/response formats

Also available at:

http://localhost:8000/redoc

A different documentation style — cleaner for reading.

This is automatically updated every time you add a new route. In NestJS you had to manually setup Swagger — here it's built in and zero config.


Understanding the Code

Let's break down main.py line by line:


    from fastapi import FastAPI

Import the FastAPI class.


    app = FastAPI()

Create the FastAPI application instance. This is your entire app.


    @app.get("/")

This is a decorator — it registers the function below as a GET route at path /.


    def read_root():
        return {"message": "Hello, World!"}

The route handler function. Whatever you return — FastAPI automatically converts it to JSON response.


    @app.get("/hello/{name}")
    def say_hello(name: str):

{name} in the path is a path parameter. FastAPI reads it and passes it to the function. The : str type hint tells FastAPI to validate it's a string.


Adding More to Your App

Update main.py and save — server auto-reloads:


    from fastapi import FastAPI

    app = FastAPI(
        title="My First FastAPI",
        description="Learning FastAPI step by step",
        version="1.0.0"
    )


    @app.get("/")
    def read_root():
        return {"message": "Hello, World!", "status": "running"}


    @app.get("/hello/{name}")
    def say_hello(name: str):
        return {"message": f"Hello, {name}!"}


    @app.get("/add/{a}/{b}")
    def add_numbers(a: int, b: int):
        return {
            "a": a,
            "b": b,
            "sum": a + b
        }


    @app.get("/info")
    def get_info():
        return {
            "framework": "FastAPI",
            "language": "Python",
            "version": "1.0.0",
            "developer": "Gagan"
        }

Visit /docs again — you'll see all 4 routes documented automatically.

Visit /add/10/25:

{"a": 10, "b": 25, "sum": 35}

FastAPI automatically converted "10" and "25" from the URL string into integers because of the : int type hints. That's the power of type hints in FastAPI.


Project Structure So Far

fastapi-learning/
├── venv/              ← virtual environment (don't touch)
├── main.py            ← your app
└── requirements.txt   ← after pip freeze

Simple for now. Will grow as we add more features.


Summary of What You Learned

In this stage you covered:

  • Python type hints — basic and modern syntax
  • Virtual environments — why and how to use them
  • Installing FastAPI and Uvicorn
  • Creating and running your first FastAPI app
  • Automatic documentation at /docs
  • Path parameters with type validation


No comments:

Post a Comment

Foundation Setup for Fast APIs - Python Type Hints

What are Type Hints? Type hints let you tell Python what type of data a variable or function expects.     # Without type hints     def a...