Skip to content

Commit f6bcf2d

Browse files
committed
Add Materials for "A Close Look at a FastAPI Example Application"
1 parent 10cc2cc commit f6bcf2d

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed

fastapi-python-web-apis/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# A Close Look at a FastAPI Example Application
2+
3+
This repository contains code snippets discussed in the associated tutorial on [A Close Look at a FastAPI Example Application](https://realpython.com/fastapi-python-web-apis/).
4+
5+
## Installation
6+
7+
The recommended way to install FastAPI is with the `[standard]` extra dependencies. This ensures you get all the tools you need for developing an API without having to hunt down additional packages later:
8+
9+
```console
10+
$ python -m pip install "fastapi[standard]"
11+
```
12+
13+
The quotes around `"fastapi[standard]"` ensure the command works correctly across different [terminals](https://realpython.com/terminal-commands/) and operating systems. With the command above you install several useful packages, including the [FastAPI CLI](https://fastapi.tiangolo.com/fastapi-cli/) and [uvicorn](https://www.uvicorn.org/), an ASGI server for running your application.
14+
15+
You can also use the `requirements.txt` file in this folder and run `python -m pip install -r requirements.txt` to install the standard dependencies of FastAPI.

fastapi-python-web-apis/main.py

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import random
2+
from string import ascii_letters, hexdigits
3+
from typing import Annotated
4+
5+
from jinja2 import Template
6+
from pydantic import BaseModel, Field
7+
8+
from fastapi import FastAPI, HTTPException, Query
9+
from fastapi.middleware.cors import CORSMiddleware
10+
from fastapi.responses import HTMLResponse, PlainTextResponse
11+
12+
tags_metadata = [
13+
{
14+
"name": "Random Playground",
15+
"description": "Operations for generating random stuff",
16+
},
17+
{
18+
"name": "Random Items Management",
19+
"description": "Create, shuffle, read, update and delete items",
20+
},
21+
]
22+
23+
app = FastAPI(
24+
title="Randomizer API",
25+
description="Generate random numbers and manage a list of items",
26+
version="1.0.0",
27+
openapi_tags=tags_metadata,
28+
)
29+
30+
app.add_middleware(
31+
CORSMiddleware,
32+
allow_origins=["http://localhost:3000", "https://example.com"],
33+
allow_credentials=True,
34+
allow_methods=["GET", "POST", "PUT", "DELETE"],
35+
allow_headers=["*"],
36+
)
37+
38+
items_db = []
39+
40+
41+
class Item(BaseModel):
42+
name: str = Field(
43+
min_length=1, max_length=100, description="The item name"
44+
)
45+
46+
47+
class ItemList(BaseModel):
48+
items: list[str] = Field(min_items=1, description="List of items")
49+
50+
51+
class ItemUpdate(BaseModel):
52+
old_item: str = Field(min_length=1, description="Item to replace")
53+
new_item: str = Field(min_length=1, description="New item name")
54+
55+
56+
class ItemResponse(BaseModel):
57+
message: str
58+
item: str
59+
total_items: int
60+
61+
62+
class ItemListResponse(BaseModel):
63+
original_order: list[str]
64+
randomized_order: list[str]
65+
count: int
66+
67+
68+
class ItemUpdateResponse(BaseModel):
69+
message: str
70+
old_item: str
71+
new_item: str
72+
73+
74+
class ItemDeleteResponse(BaseModel):
75+
message: str
76+
deleted_item: str
77+
remaining_items: int
78+
79+
80+
@app.get("/", tags=["Random Playground"])
81+
async def home():
82+
return {"message": "Welcome to the Randomizer API"}
83+
84+
85+
@app.get("/random/{max_value}", tags=["Random Playground"])
86+
async def get_random_number(max_value: int):
87+
return {"max": max_value, "random_number": random.randint(1, max_value)}
88+
89+
90+
@app.get("/random-between", tags=["Random Playground"])
91+
def get_random_number_between(
92+
min_value: Annotated[
93+
int | None,
94+
Query(
95+
title="Minimum Value",
96+
description="The minimum random number",
97+
ge=1,
98+
le=99,
99+
),
100+
] = 1,
101+
max_value: Annotated[
102+
int | None,
103+
Query(
104+
title="Maximum Value",
105+
description="The maximum random number",
106+
ge=1,
107+
le=1000,
108+
),
109+
] = 99,
110+
):
111+
if min_value > max_value:
112+
return {"error": "min_value cannot be greater than max_value"}
113+
114+
return {
115+
"min": min_value,
116+
"max": max_value,
117+
"random_number": random.randint(min_value, max_value),
118+
}
119+
120+
121+
@app.get("/random-string/{length}", tags=["Random Playground"])
122+
def generate_random_string(length: int):
123+
return PlainTextResponse("".join(random.choices(ascii_letters, k=length)))
124+
125+
126+
@app.get(
127+
"/random-color", response_class=HTMLResponse, tags=["Random Playground"]
128+
)
129+
def random_color():
130+
hex_chars = "".join(random.choice(hexdigits.lower()) for _ in range(6))
131+
hex_color = f"#{hex_chars}"
132+
template_string = """
133+
<!DOCTYPE html>
134+
<html lang="en">
135+
<head>
136+
<meta charset="UTF-8">
137+
<title>Random Color: {{ color }}</title>
138+
<style>
139+
body {
140+
height: 100vh;
141+
display: flex;
142+
justify-content: center;
143+
align-items: center;
144+
background-color: {{ color }};
145+
color: white;
146+
font-size: 120px;
147+
font-family: monospace;
148+
}
149+
</style>
150+
</head>
151+
<body>
152+
<div>{{ color }}</div>
153+
</body>
154+
</html>
155+
"""
156+
157+
template = Template(template_string)
158+
html_content = template.render(color=hex_color)
159+
160+
return html_content
161+
162+
163+
@app.post(
164+
"/items", response_model=ItemResponse, tags=["Random Items Management"]
165+
)
166+
def add_item(item: Item):
167+
if item.name in items_db:
168+
raise HTTPException(status_code=400, detail="Item already exists")
169+
170+
items_db.append(item.name)
171+
return ItemResponse(
172+
message="Item added successfully",
173+
item=item.name,
174+
total_items=len(items_db),
175+
)
176+
177+
178+
@app.get(
179+
"/items/random",
180+
response_model=ItemListResponse,
181+
tags=["Random Items Management"],
182+
)
183+
def get_randomized_items():
184+
if not items_db:
185+
return ItemListResponse(
186+
original_order=[], randomized_order=[], count=0
187+
)
188+
189+
randomized = items_db.copy()
190+
random.shuffle(randomized)
191+
192+
return ItemListResponse(
193+
original_order=items_db,
194+
randomized_order=randomized,
195+
count=len(items_db),
196+
)
197+
198+
199+
@app.put(
200+
"/items",
201+
response_model=ItemUpdateResponse,
202+
tags=["Random Items Management"],
203+
)
204+
def update_item(item_update: ItemUpdate):
205+
if item_update.old_item not in items_db:
206+
raise HTTPException(status_code=404, detail="Item not found")
207+
208+
index = items_db.index(item_update.old_item)
209+
items_db[index] = item_update.new_item
210+
211+
return ItemUpdateResponse(
212+
message="Item updated successfully",
213+
old_item=item_update.old_item,
214+
new_item=item_update.new_item,
215+
)
216+
217+
218+
@app.delete(
219+
"/items/{item}",
220+
response_model=ItemDeleteResponse,
221+
tags=["Random Items Management"],
222+
)
223+
def delete_item(item: str):
224+
if item not in items_db:
225+
raise HTTPException(status_code=404, detail="Item not found")
226+
227+
items_db.remove(item)
228+
229+
return ItemDeleteResponse(
230+
message="Item deleted successfully",
231+
deleted_item=item,
232+
remaining_items=len(items_db),
233+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from fastapi import FastAPI
2+
3+
app = FastAPI()
4+
5+
items_db = []
6+
7+
8+
@app.get("/")
9+
def home():
10+
return {"message": "Welcome to the Randomizer API"}
11+
12+
13+
@app.post("/items")
14+
def add_item(item: str):
15+
if not item.strip():
16+
raise HTTPException(status_code=400, detail="Item cannot be empty")
17+
18+
if item in items_db:
19+
raise HTTPException(status_code=400, detail="Item already exists")
20+
21+
items_db.append(item)
22+
return {
23+
"message": "Item added successfully",
24+
"item": item,
25+
"total_items": len(items_db),
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
annotated-types==0.7.0
2+
anyio==4.10.0
3+
certifi==2025.8.3
4+
click==8.2.1
5+
dnspython==2.7.0
6+
email_validator==2.2.0
7+
fastapi==0.116.1
8+
fastapi-cli==0.0.8
9+
fastapi-cloud-cli==0.1.5
10+
h11==0.16.0
11+
httpcore==1.0.9
12+
httptools==0.6.4
13+
httpx==0.28.1
14+
idna==3.10
15+
Jinja2==3.1.6
16+
markdown-it-py==4.0.0
17+
MarkupSafe==3.0.2
18+
mdurl==0.1.2
19+
pydantic==2.11.7
20+
pydantic_core==2.33.2
21+
Pygments==2.19.2
22+
python-dotenv==1.1.1
23+
python-multipart==0.0.20
24+
PyYAML==6.0.2
25+
rich==14.1.0
26+
rich-toolkit==0.15.0
27+
rignore==0.6.4
28+
sentry-sdk==2.34.1
29+
shellingham==1.5.4
30+
sniffio==1.3.1
31+
starlette==0.47.2
32+
typer==0.16.0
33+
typing-inspection==0.4.1
34+
typing_extensions==4.14.1
35+
urllib3==2.5.0
36+
uvicorn==0.35.0
37+
uvloop==0.21.0
38+
watchfiles==1.1.0
39+
websockets==15.0.1

0 commit comments

Comments
 (0)