Skip to content

Commit bad2739

Browse files
Copilotks6088ts
andcommitted
Complete FastAPI refactoring - organize code into modular structure
Co-authored-by: ks6088ts <[email protected]>
1 parent c8ba2ce commit bad2739

File tree

10 files changed

+309
-204
lines changed

10 files changed

+309
-204
lines changed

template_fastapi/app.py

Lines changed: 6 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@
33
ref. https://github.com/tadata-org/fastapi_mcp/blob/v0.3.4/examples/shared/apps/items.py
44
"""
55

6-
import random
76
import uuid
87
from os import getenv
98

109
from azure.monitor.opentelemetry import configure_azure_monitor
11-
from fastapi import FastAPI, HTTPException, Query
10+
from fastapi import FastAPI
1211
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
1312
from opentelemetry.trace import Span
14-
from pydantic import BaseModel
1513

16-
from template_fastapi.opentelemetry import get_meter, get_tracer
14+
from template_fastapi.routers import demo, games, items
1715

18-
tracer = get_tracer(__name__)
19-
meter = get_meter(__name__)
2016
app = FastAPI()
2117

2218
# If APPLICATIONINSIGHTS_CONNECTION_STRING exists, configure Azure Monitor
@@ -39,201 +35,7 @@ def server_request_hook(span: Span, scope: dict):
3935
)
4036
FastAPIInstrumentor.instrument_app(app)
4137

42-
43-
class Item(BaseModel):
44-
id: int
45-
name: str
46-
description: str | None = None
47-
price: float
48-
tags: list[str] = []
49-
50-
51-
items_db: dict[int, Item] = {}
52-
53-
54-
@app.get("/items/", response_model=list[Item], tags=["items"], operation_id="list_items")
55-
async def list_items(skip: int = 0, limit: int = 10):
56-
"""
57-
List all items in the database.
58-
59-
Returns a list of items, with pagination support.
60-
"""
61-
return list(items_db.values())[skip : skip + limit]
62-
63-
64-
@app.get("/items/{item_id}", response_model=Item, tags=["items"], operation_id="get_item")
65-
async def read_item(item_id: int):
66-
"""
67-
Get a specific item by its ID.
68-
69-
Raises a 404 error if the item does not exist.
70-
"""
71-
if item_id not in items_db:
72-
raise HTTPException(status_code=404, detail="Item not found")
73-
return items_db[item_id]
74-
75-
76-
@app.post("/items/", response_model=Item, tags=["items"], operation_id="create_item")
77-
async def create_item(item: Item):
78-
"""
79-
Create a new item in the database.
80-
81-
Returns the created item with its assigned ID.
82-
"""
83-
items_db[item.id] = item
84-
return item
85-
86-
87-
@app.put("/items/{item_id}", response_model=Item, tags=["items"], operation_id="update_item")
88-
async def update_item(item_id: int, item: Item):
89-
"""
90-
Update an existing item.
91-
92-
Raises a 404 error if the item does not exist.
93-
"""
94-
if item_id not in items_db:
95-
raise HTTPException(status_code=404, detail="Item not found")
96-
97-
item.id = item_id
98-
items_db[item_id] = item
99-
return item
100-
101-
102-
@app.delete("/items/{item_id}", tags=["items"], operation_id="delete_item")
103-
async def delete_item(item_id: int):
104-
"""
105-
Delete an item from the database.
106-
107-
Raises a 404 error if the item does not exist.
108-
"""
109-
if item_id not in items_db:
110-
raise HTTPException(status_code=404, detail="Item not found")
111-
112-
del items_db[item_id]
113-
return {"message": "Item deleted successfully"}
114-
115-
116-
@app.get("/items/search/", response_model=list[Item], tags=["search"], operation_id="search_items")
117-
async def search_items(
118-
q: str | None = Query(None, description="Search query string"),
119-
min_price: float | None = Query(None, description="Minimum price"),
120-
max_price: float | None = Query(None, description="Maximum price"),
121-
tags: list[str] = Query([], description="Filter by tags"),
122-
):
123-
"""
124-
Search for items with various filters.
125-
126-
Returns a list of items that match the search criteria.
127-
"""
128-
results = list(items_db.values())
129-
130-
if q:
131-
q = q.lower()
132-
results = [
133-
item
134-
for item in results
135-
if q in item.name.lower() or (item.description is not None and q in item.description.lower())
136-
]
137-
138-
if min_price is not None:
139-
results = [item for item in results if item.price >= min_price]
140-
if max_price is not None:
141-
results = [item for item in results if item.price <= max_price]
142-
143-
if tags:
144-
results = [item for item in results if all(tag in item.tags for tag in tags)]
145-
146-
return results
147-
148-
149-
sample_items = [
150-
Item(id=1, name="Hammer", description="A tool for hammering nails", price=9.99, tags=["tool", "hardware"]),
151-
Item(id=2, name="Screwdriver", description="A tool for driving screws", price=7.99, tags=["tool", "hardware"]),
152-
Item(id=3, name="Wrench", description="A tool for tightening bolts", price=12.99, tags=["tool", "hardware"]),
153-
Item(id=4, name="Saw", description="A tool for cutting wood", price=19.99, tags=["tool", "hardware", "cutting"]),
154-
Item(id=5, name="Drill", description="A tool for drilling holes", price=49.99, tags=["tool", "hardware", "power"]),
155-
]
156-
for item in sample_items:
157-
items_db[item.id] = item
158-
159-
160-
# Add flaky API which receives percentage of failure
161-
@app.get("/flaky/{failure_rate}", tags=["flaky"], operation_id="flaky")
162-
async def flaky(failure_rate: int):
163-
"""
164-
A flaky endpoint that simulates a failure based on the provided failure rate.
165-
166-
The failure rate is a percentage (0-100) that determines the likelihood of failure.
167-
"""
168-
if not (0 <= failure_rate <= 100):
169-
raise HTTPException(
170-
status_code=400,
171-
detail="Failure rate must be between 0 and 100",
172-
)
173-
174-
if random.randint(0, 100) < failure_rate:
175-
raise HTTPException(
176-
status_code=500,
177-
detail="Simulated failure",
178-
)
179-
180-
return {
181-
"message": "Request succeeded",
182-
}
183-
184-
185-
# Add flaky API which raises an exception
186-
@tracer.start_as_current_span("flaky_exception")
187-
@app.get("/flaky/exception", tags=["flaky"], operation_id="flaky_exception")
188-
async def flaky_exception():
189-
"""
190-
A flaky endpoint that always raises an exception.
191-
"""
192-
raise HTTPException(
193-
status_code=500,
194-
detail="Simulated exception",
195-
)
196-
197-
198-
# Add a heavy synchronous endpoint which receives milliseconds to sleep
199-
@app.get("/heavy_sync/{sleep_ms}", tags=["heavy"], operation_id="heavy_sync_with_sleep")
200-
async def heavy_sync_with_sleep(sleep_ms: int):
201-
"""
202-
A heavy synchronous endpoint that sleeps for the specified number of milliseconds.
203-
204-
This simulates a long-running synchronous operation.
205-
"""
206-
if sleep_ms < 0:
207-
raise HTTPException(
208-
status_code=400,
209-
detail="Sleep time must be a non-negative integer",
210-
)
211-
212-
import time
213-
214-
with tracer.start_as_current_span("parent"):
215-
print(f"Sleeping for {sleep_ms} milliseconds")
216-
time.sleep(sleep_ms / 1000.0)
217-
with tracer.start_as_current_span("child"):
218-
print("Child span")
219-
return {
220-
"message": f"Slept for {sleep_ms} milliseconds",
221-
}
222-
223-
224-
# Add counter for dice rolls
225-
roll_counter = meter.create_counter(
226-
"dice.rolls",
227-
description="The number of rolls by roll value",
228-
)
229-
230-
231-
@app.get("/roll_dice", operation_id="roll_dice")
232-
async def roll_dice():
233-
"""
234-
Simulate rolling a dice and record the roll in the meter.
235-
"""
236-
with tracer.start_as_current_span("roll_dice"):
237-
roll = random.randint(1, 6)
238-
roll_counter.add(1, {"roll.value": str(roll)})
239-
return roll
38+
# Include routers
39+
app.include_router(items.router)
40+
app.include_router(demo.router)
41+
app.include_router(games.router)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Database module for FastAPI application

template_fastapi/database/items.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from template_fastapi.models.item import Item
2+
3+
# In-memory database for items
4+
items_db: dict[int, Item] = {}
5+
6+
# Sample data initialization
7+
sample_items = [
8+
Item(id=1, name="Hammer", description="A tool for hammering nails", price=9.99, tags=["tool", "hardware"]),
9+
Item(id=2, name="Screwdriver", description="A tool for driving screws", price=7.99, tags=["tool", "hardware"]),
10+
Item(id=3, name="Wrench", description="A tool for tightening bolts", price=12.99, tags=["tool", "hardware"]),
11+
Item(id=4, name="Saw", description="A tool for cutting wood", price=19.99, tags=["tool", "hardware", "cutting"]),
12+
Item(id=5, name="Drill", description="A tool for drilling holes", price=49.99, tags=["tool", "hardware", "power"]),
13+
]
14+
15+
# Initialize database with sample data
16+
for item in sample_items:
17+
items_db[item.id] = item
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Models module for FastAPI application

template_fastapi/models/item.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pydantic import BaseModel
2+
3+
4+
class Item(BaseModel):
5+
id: int
6+
name: str
7+
description: str | None = None
8+
price: float
9+
tags: list[str] = []
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Routers module for FastAPI application

template_fastapi/routers/demo.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import random
2+
import time
3+
4+
from fastapi import APIRouter, HTTPException
5+
6+
from template_fastapi.opentelemetry import get_tracer
7+
8+
tracer = get_tracer(__name__)
9+
router = APIRouter()
10+
11+
12+
@tracer.start_as_current_span("flaky_exception")
13+
@router.get("/flaky/exception", tags=["flaky"], operation_id="flaky_exception")
14+
async def flaky_exception():
15+
"""
16+
A flaky endpoint that always raises an exception.
17+
"""
18+
raise HTTPException(
19+
status_code=500,
20+
detail="Simulated exception",
21+
)
22+
23+
24+
@router.get("/flaky/{failure_rate}", tags=["flaky"], operation_id="flaky")
25+
async def flaky(failure_rate: int):
26+
"""
27+
A flaky endpoint that simulates a failure based on the provided failure rate.
28+
29+
The failure rate is a percentage (0-100) that determines the likelihood of failure.
30+
"""
31+
if not (0 <= failure_rate <= 100):
32+
raise HTTPException(
33+
status_code=400,
34+
detail="Failure rate must be between 0 and 100",
35+
)
36+
37+
if random.randint(0, 100) < failure_rate:
38+
raise HTTPException(
39+
status_code=500,
40+
detail="Simulated failure",
41+
)
42+
43+
return {
44+
"message": "Request succeeded",
45+
}
46+
47+
48+
@router.get("/heavy_sync/{sleep_ms}", tags=["heavy"], operation_id="heavy_sync_with_sleep")
49+
async def heavy_sync_with_sleep(sleep_ms: int):
50+
"""
51+
A heavy synchronous endpoint that sleeps for the specified number of milliseconds.
52+
53+
This simulates a long-running synchronous operation.
54+
"""
55+
if sleep_ms < 0:
56+
raise HTTPException(
57+
status_code=400,
58+
detail="Sleep time must be a non-negative integer",
59+
)
60+
61+
with tracer.start_as_current_span("parent"):
62+
print(f"Sleeping for {sleep_ms} milliseconds")
63+
time.sleep(sleep_ms / 1000.0)
64+
with tracer.start_as_current_span("child"):
65+
print("Child span")
66+
return {
67+
"message": f"Slept for {sleep_ms} milliseconds",
68+
}

template_fastapi/routers/games.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import random
2+
3+
from fastapi import APIRouter
4+
5+
from template_fastapi.opentelemetry import get_meter, get_tracer
6+
7+
tracer = get_tracer(__name__)
8+
meter = get_meter(__name__)
9+
10+
# Add counter for dice rolls
11+
roll_counter = meter.create_counter(
12+
"dice.rolls",
13+
description="The number of rolls by roll value",
14+
)
15+
16+
router = APIRouter()
17+
18+
19+
@router.get("/roll_dice", operation_id="roll_dice")
20+
async def roll_dice():
21+
"""
22+
Simulate rolling a dice and record the roll in the meter.
23+
"""
24+
with tracer.start_as_current_span("roll_dice"):
25+
roll = random.randint(1, 6)
26+
roll_counter.add(1, {"roll.value": str(roll)})
27+
return roll

0 commit comments

Comments
 (0)